Revision: miles@gnu.org--gnu-2005/gnus--devo--0--patch-44
[gnus] / lisp / mm-util.el
1 ;;; mm-util.el --- Utility functions for Mule and low level things
2 ;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005
3 ;;   Free Software Foundation, Inc.
4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;;      MORIOKA Tomohiko <morioka@jaist.ac.jp>
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
23
24 ;;; Commentary:
25
26 ;;; Code:
27
28 (eval-when-compile (require 'cl))
29 (require 'mail-prsvr)
30
31 (eval-and-compile
32   (mapcar
33    (lambda (elem)
34      (let ((nfunc (intern (format "mm-%s" (car elem)))))
35        (if (fboundp (car elem))
36            (defalias nfunc (car elem))
37          (defalias nfunc (cdr elem)))))
38    '((decode-coding-string . (lambda (s a) s))
39      (encode-coding-string . (lambda (s a) s))
40      (encode-coding-region . ignore)
41      (coding-system-list . ignore)
42      (decode-coding-region . ignore)
43      (char-int . identity)
44      (coding-system-equal . equal)
45      (annotationp . ignore)
46      (set-buffer-file-coding-system . ignore)
47      (read-charset
48       . (lambda (prompt)
49           "Return a charset."
50           (intern
51            (completing-read
52             prompt
53             (mapcar (lambda (e) (list (symbol-name (car e))))
54                     mm-mime-mule-charset-alist)
55             nil t))))
56      (subst-char-in-string
57       . (lambda (from to string &optional inplace) ;; stolen (and renamed) from nnheader.el
58           "Replace characters in STRING from FROM to TO.
59           Unless optional argument INPLACE is non-nil, return a new string."
60           (let ((string (if inplace string (copy-sequence string)))
61                 (len (length string))
62                 (idx 0))
63             ;; Replace all occurrences of FROM with TO.
64             (while (< idx len)
65               (when (= (aref string idx) from)
66                 (aset string idx to))
67               (setq idx (1+ idx)))
68             string)))
69      (replace-in-string
70       . (lambda (string regexp rep &optional literal)
71           "See `replace-regexp-in-string', only the order of args differs."
72           (replace-regexp-in-string regexp rep string nil literal)))
73      (string-as-unibyte . identity)
74      (string-make-unibyte . identity)
75      (string-as-multibyte . identity)
76      (string-to-multibyte
77       . (lambda (string)
78           "Return a multibyte string with the same individual chars as string."
79           (mapconcat
80            (lambda (ch) (mm-string-as-multibyte (char-to-string ch)))
81            string "")))
82      (multibyte-string-p . ignore)
83      ;; It is not a MIME function, but some MIME functions use it.
84      (make-temp-file . (lambda (prefix &optional dir-flag)
85                          (let ((file (expand-file-name
86                                       (make-temp-name prefix)
87                                       (if (fboundp 'temp-directory)
88                                           (temp-directory)
89                                         temporary-file-directory))))
90                            (if dir-flag
91                                (make-directory file))
92                            file)))
93      (insert-byte . insert-char)
94      (multibyte-char-to-unibyte . identity))))
95
96 (eval-and-compile
97   (defalias 'mm-char-or-char-int-p
98     (cond
99      ((fboundp 'char-or-char-int-p) 'char-or-char-int-p)
100      ((fboundp 'char-valid-p) 'char-valid-p)
101      (t 'identity))))
102
103 ;; Fixme:  This seems always to be used to read a MIME charset, so it
104 ;; should be re-named and fixed (in Emacs) to offer completion only on
105 ;; proper charset names (base coding systems which have a
106 ;; mime-charset defined).  XEmacs doesn't believe in mime-charset;
107 ;; test with
108 ;;   `(or (coding-system-get 'iso-8859-1 'mime-charset)
109 ;;        (coding-system-get 'iso-8859-1 :mime-charset))'
110 ;; Actually, there should be an `mm-coding-system-mime-charset'.
111 (eval-and-compile
112   (defalias 'mm-read-coding-system
113     (cond
114      ((fboundp 'read-coding-system)
115       (if (and (featurep 'xemacs)
116                (<= (string-to-number emacs-version) 21.1))
117           (lambda (prompt &optional default-coding-system)
118             (read-coding-system prompt))
119         'read-coding-system))
120      (t (lambda (prompt &optional default-coding-system)
121           "Prompt the user for a coding system."
122           (completing-read
123            prompt (mapcar (lambda (s) (list (symbol-name (car s))))
124                           mm-mime-mule-charset-alist)))))))
125
126 (defvar mm-coding-system-list nil)
127 (defun mm-get-coding-system-list ()
128   "Get the coding system list."
129   (or mm-coding-system-list
130       (setq mm-coding-system-list (mm-coding-system-list))))
131
132 (defun mm-coding-system-p (cs)
133   "Return non-nil if CS is a symbol naming a coding system.
134 In XEmacs, also return non-nil if CS is a coding system object.
135 If CS is available, return CS itself in Emacs, and return a coding
136 system object in XEmacs."
137   (if (fboundp 'find-coding-system)
138       (find-coding-system cs)
139     (if (fboundp 'coding-system-p)
140         (when (coding-system-p cs)
141           cs)
142       ;; Is this branch ever actually useful?
143       (car (memq cs (mm-get-coding-system-list))))))
144
145 (defvar mm-charset-synonym-alist
146   `(
147     ;; Not in XEmacs, but it's not a proper MIME charset anyhow.
148     ,@(unless (mm-coding-system-p 'x-ctext)
149        '((x-ctext . ctext)))
150     ;; ISO-8859-15 is very similar to ISO-8859-1.  But it's _different_!
151     ,@(unless (mm-coding-system-p 'iso-8859-15)
152        '((iso-8859-15 . iso-8859-1)))
153     ;; BIG-5HKSCS is similar to, but different than, BIG-5.
154     ,@(unless (mm-coding-system-p 'big5-hkscs)
155         '((big5-hkscs . big5)))
156     ;; Windows-1252 is actually a superset of Latin-1.  See also
157     ;; `gnus-article-dumbquotes-map'.
158     ,@(unless (mm-coding-system-p 'windows-1252)
159        (if (mm-coding-system-p 'cp1252)
160            '((windows-1252 . cp1252))
161          '((windows-1252 . iso-8859-1))))
162     ;; Windows-1250 is a variant of Latin-2 heavily used by Microsoft
163     ;; Outlook users in Czech republic. Use this to allow reading of their
164     ;; e-mails. cp1250 should be defined by M-x codepage-setup.
165     ,@(if (and (not (mm-coding-system-p 'windows-1250))
166                (mm-coding-system-p 'cp1250))
167           '((windows-1250 . cp1250)))
168     ;; A Microsoft misunderstanding.
169     ,@(if (and (not (mm-coding-system-p 'unicode))
170                (mm-coding-system-p 'utf-16-le))
171           '((unicode . utf-16-le)))
172     ;; A Microsoft misunderstanding.
173     ,@(unless (mm-coding-system-p 'ks_c_5601-1987)
174         (if (mm-coding-system-p 'cp949)
175             '((ks_c_5601-1987 . cp949))
176           '((ks_c_5601-1987 . euc-kr))))
177     )
178   "A mapping from invalid charset names to the real charset names.")
179
180 (defvar mm-binary-coding-system
181   (cond
182    ((mm-coding-system-p 'binary) 'binary)
183    ((mm-coding-system-p 'no-conversion) 'no-conversion)
184    (t nil))
185   "100% binary coding system.")
186
187 (defvar mm-text-coding-system
188   (or (if (memq system-type '(windows-nt ms-dos ms-windows))
189           (and (mm-coding-system-p 'raw-text-dos) 'raw-text-dos)
190         (and (mm-coding-system-p 'raw-text) 'raw-text))
191       mm-binary-coding-system)
192   "Text-safe coding system (For removing ^M).")
193
194 (defvar mm-text-coding-system-for-write nil
195   "Text coding system for write.")
196
197 (defvar mm-auto-save-coding-system
198   (cond
199    ((mm-coding-system-p 'utf-8-emacs)   ; Mule 7
200     (if (memq system-type '(windows-nt ms-dos ms-windows))
201         (if (mm-coding-system-p 'utf-8-emacs-dos)
202             'utf-8-emacs-dos mm-binary-coding-system)
203       'utf-8-emacs))
204    ((mm-coding-system-p 'emacs-mule)
205     (if (memq system-type '(windows-nt ms-dos ms-windows))
206         (if (mm-coding-system-p 'emacs-mule-dos)
207             'emacs-mule-dos mm-binary-coding-system)
208       'emacs-mule))
209    ((mm-coding-system-p 'escape-quoted) 'escape-quoted)
210    (t mm-binary-coding-system))
211   "Coding system of auto save file.")
212
213 (defvar mm-universal-coding-system mm-auto-save-coding-system
214   "The universal coding system.")
215
216 ;; Fixme: some of the cars here aren't valid MIME charsets.  That
217 ;; should only matter with XEmacs, though.
218 (defvar mm-mime-mule-charset-alist
219   `((us-ascii ascii)
220     (iso-8859-1 latin-iso8859-1)
221     (iso-8859-2 latin-iso8859-2)
222     (iso-8859-3 latin-iso8859-3)
223     (iso-8859-4 latin-iso8859-4)
224     (iso-8859-5 cyrillic-iso8859-5)
225     ;; Non-mule (X)Emacs uses the last mule-charset for 8bit characters.
226     ;; The fake mule-charset, gnus-koi8-r, tells Gnus that the default
227     ;; charset is koi8-r, not iso-8859-5.
228     (koi8-r cyrillic-iso8859-5 gnus-koi8-r)
229     (iso-8859-6 arabic-iso8859-6)
230     (iso-8859-7 greek-iso8859-7)
231     (iso-8859-8 hebrew-iso8859-8)
232     (iso-8859-9 latin-iso8859-9)
233     (iso-8859-14 latin-iso8859-14)
234     (iso-8859-15 latin-iso8859-15)
235     (viscii vietnamese-viscii-lower)
236     (iso-2022-jp latin-jisx0201 japanese-jisx0208 japanese-jisx0208-1978)
237     (euc-kr korean-ksc5601)
238     (gb2312 chinese-gb2312)
239     (big5 chinese-big5-1 chinese-big5-2)
240     (tibetan tibetan)
241     (thai-tis620 thai-tis620)
242     (windows-1251 cyrillic-iso8859-5)
243     (iso-2022-7bit ethiopic arabic-1-column arabic-2-column)
244     (iso-2022-jp-2 latin-iso8859-1 greek-iso8859-7
245                    latin-jisx0201 japanese-jisx0208-1978
246                    chinese-gb2312 japanese-jisx0208
247                    korean-ksc5601 japanese-jisx0212)
248     (iso-2022-int-1 latin-iso8859-1 greek-iso8859-7
249                     latin-jisx0201 japanese-jisx0208-1978
250                     chinese-gb2312 japanese-jisx0208
251                     korean-ksc5601 japanese-jisx0212
252                     chinese-cns11643-1 chinese-cns11643-2)
253     (iso-2022-int-1 latin-iso8859-1 latin-iso8859-2
254                     cyrillic-iso8859-5 greek-iso8859-7
255                     latin-jisx0201 japanese-jisx0208-1978
256                     chinese-gb2312 japanese-jisx0208
257                     korean-ksc5601 japanese-jisx0212
258                     chinese-cns11643-1 chinese-cns11643-2
259                     chinese-cns11643-3 chinese-cns11643-4
260                     chinese-cns11643-5 chinese-cns11643-6
261                     chinese-cns11643-7)
262     (iso-2022-jp-3 latin-jisx0201 japanese-jisx0208-1978 japanese-jisx0208
263                    japanese-jisx0213-1 japanese-jisx0213-2)
264     (shift_jis latin-jisx0201 katakana-jisx0201 japanese-jisx0208)
265     ,(if (or (not (fboundp 'charsetp)) ;; non-Mule case
266              (charsetp 'unicode-a)
267              (not (mm-coding-system-p 'mule-utf-8)))
268          '(utf-8 unicode-a unicode-b unicode-c unicode-d unicode-e)
269        ;; If we have utf-8 we're in Mule 5+.
270        (append '(utf-8)
271                (delete 'ascii
272                        (coding-system-get 'mule-utf-8 'safe-charsets)))))
273   "Alist of MIME-charset/MULE-charsets.")
274
275 (defun mm-enrich-utf-8-by-mule-ucs ()
276   "Make the `utf-8' MIME charset usable by the Mule-UCS package.
277 This function will run when the `un-define' module is loaded under
278 XEmacs, and fill the `utf-8' entry in `mm-mime-mule-charset-alist'
279 with Mule charsets.  It is completely useless for Emacs."
280   (unless (cdr (delete '(mm-enrich-utf-8-by-mule-ucs)
281                        (assoc "un-define" after-load-alist)))
282     (setq after-load-alist
283           (delete '("un-define") after-load-alist)))
284   (when (boundp 'unicode-basic-translation-charset-order-list)
285     (condition-case nil
286         (let ((val (delq
287                     'ascii
288                     (copy-sequence
289                      (symbol-value
290                       'unicode-basic-translation-charset-order-list))))
291               (elem (assq 'utf-8 mm-mime-mule-charset-alist)))
292           (if elem
293               (setcdr elem val)
294             (setq mm-mime-mule-charset-alist
295                   (nconc mm-mime-mule-charset-alist
296                          (list (cons 'utf-8 val))))))
297       (error))))
298
299 ;; Correct by construction, but should be unnecessary for Emacs:
300 (if (featurep 'xemacs)
301     (eval-after-load "un-define" '(mm-enrich-utf-8-by-mule-ucs))
302   (when (and (fboundp 'coding-system-list)
303              (fboundp 'sort-coding-systems))
304     (let ((css (sort-coding-systems (coding-system-list 'base-only)))
305           cs mime mule alist)
306       (while css
307         (setq cs (pop css)
308               mime (or (coding-system-get cs :mime-charset) ; Emacs 22
309                        (coding-system-get cs 'mime-charset)))
310         (when (and mime
311                    (not (eq t (setq mule
312                                     (coding-system-get cs 'safe-charsets))))
313                    (not (assq mime alist)))
314           (push (cons mime (delq 'ascii mule)) alist)))
315       (setq mm-mime-mule-charset-alist (nreverse alist)))))
316
317 (defvar mm-hack-charsets '(iso-8859-15 iso-2022-jp-2)
318   "A list of special charsets.
319 Valid elements include:
320 `iso-8859-15'    convert ISO-8859-1, -9 to ISO-8859-15 if ISO-8859-15 exists.
321 `iso-2022-jp-2'  convert ISO-2022-jp to ISO-2022-jp-2 if ISO-2022-jp-2 exists."
322 )
323
324 (defvar mm-iso-8859-15-compatible
325   '((iso-8859-1 "\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE")
326     (iso-8859-9 "\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE\xD0\xDD\xDE\xF0\xFD\xFE"))
327   "ISO-8859-15 exchangeable coding systems and inconvertible characters.")
328
329 (defvar mm-iso-8859-x-to-15-table
330   (and (fboundp 'coding-system-p)
331        (mm-coding-system-p 'iso-8859-15)
332        (mapcar
333         (lambda (cs)
334           (if (mm-coding-system-p (car cs))
335               (let ((c (string-to-char
336                         (decode-coding-string "\341" (car cs)))))
337                 (cons (char-charset c)
338                       (cons
339                        (- (string-to-char
340                            (decode-coding-string "\341" 'iso-8859-15)) c)
341                        (string-to-list (decode-coding-string (car (cdr cs))
342                                                              (car cs))))))
343             '(gnus-charset 0)))
344         mm-iso-8859-15-compatible))
345   "A table of the difference character between ISO-8859-X and ISO-8859-15.")
346
347 (defcustom mm-coding-system-priorities
348   (if (boundp 'current-language-environment)
349       (let ((lang (symbol-value 'current-language-environment)))
350         (cond ((string= lang "Japanese")
351                ;; Japanese users prefer iso-2022-jp to euc-japan or
352                ;; shift_jis, however iso-8859-1 should be used when
353                ;; there are only ASCII text and Latin-1 characters.
354                '(iso-8859-1 iso-2022-jp iso-2022-jp-2 shift_jis utf-8)))))
355   "Preferred coding systems for encoding outgoing messages.
356
357 More than one suitable coding system may be found for some text.
358 By default, the coding system with the highest priority is used
359 to encode outgoing messages (see `sort-coding-systems').  If this
360 variable is set, it overrides the default priority."
361   :version "21.2"
362   :type '(repeat (symbol :tag "Coding system"))
363   :group 'mime)
364
365 ;; ??
366 (defvar mm-use-find-coding-systems-region
367   (fboundp 'find-coding-systems-region)
368   "Use `find-coding-systems-region' to find proper coding systems.
369
370 Setting it to nil is useful on Emacsen supporting Unicode if sending
371 mail with multiple parts is preferred to sending a Unicode one.")
372
373 ;;; Internal variables:
374
375 ;;; Functions:
376
377 (defun mm-mule-charset-to-mime-charset (charset)
378   "Return the MIME charset corresponding to the given Mule CHARSET."
379   (if (and (fboundp 'find-coding-systems-for-charsets)
380            (fboundp 'sort-coding-systems))
381       (let ((css (sort (sort-coding-systems
382                         (find-coding-systems-for-charsets (list charset)))
383                        'mm-sort-coding-systems-predicate))
384             cs mime)
385         (while (and (not mime)
386                     css)
387           (when (setq cs (pop css))
388             (setq mime (or (coding-system-get cs :mime-charset)
389                            (coding-system-get cs 'mime-charset)))))
390         mime)
391     (let ((alist (mapcar (lambda (cs)
392                            (assq cs mm-mime-mule-charset-alist))
393                          (sort (mapcar 'car mm-mime-mule-charset-alist)
394                                'mm-sort-coding-systems-predicate)))
395           out)
396       (while alist
397         (when (memq charset (cdar alist))
398           (setq out (caar alist)
399                 alist nil))
400         (pop alist))
401       out)))
402
403 (defun mm-charset-to-coding-system (charset &optional lbt)
404   "Return coding-system corresponding to CHARSET.
405 CHARSET is a symbol naming a MIME charset.
406 If optional argument LBT (`unix', `dos' or `mac') is specified, it is
407 used as the line break code type of the coding system."
408   (when (stringp charset)
409     (setq charset (intern (downcase charset))))
410   (when lbt
411     (setq charset (intern (format "%s-%s" charset lbt))))
412   (cond
413    ((null charset)
414     charset)
415    ;; Running in a non-MULE environment.
416    ((or (null (mm-get-coding-system-list))
417         (not (fboundp 'coding-system-get)))
418     charset)
419    ;; ascii
420    ((eq charset 'us-ascii)
421     'ascii)
422    ;; Check to see whether we can handle this charset.  (This depends
423    ;; on there being some coding system matching each `mime-charset'
424    ;; property defined, as there should be.)
425    ((and (mm-coding-system-p charset)
426 ;;; Doing this would potentially weed out incorrect charsets.
427 ;;;      charset
428 ;;;      (eq charset (coding-system-get charset 'mime-charset))
429          )
430     charset)
431    ;; Translate invalid charsets.
432    ((let ((cs (cdr (assq charset mm-charset-synonym-alist))))
433       (and cs (mm-coding-system-p cs) cs)))
434    ;; Last resort: search the coding system list for entries which
435    ;; have the right mime-charset in case the canonical name isn't
436    ;; defined (though it should be).
437    ((let (cs)
438       ;; mm-get-coding-system-list returns a list of cs without lbt.
439       ;; Do we need -lbt?
440       (dolist (c (mm-get-coding-system-list))
441         (if (and (null cs)
442                  (eq charset (or (coding-system-get c :mime-charset)
443                                  (coding-system-get c 'mime-charset))))
444             (setq cs c)))
445       cs))))
446
447 (eval-and-compile
448   (defvar mm-emacs-mule (and (not (featurep 'xemacs))
449                              (boundp 'default-enable-multibyte-characters)
450                              default-enable-multibyte-characters
451                              (fboundp 'set-buffer-multibyte))
452     "True in Emacs with Mule.")
453
454   (if mm-emacs-mule
455       (defun mm-enable-multibyte ()
456         "Set the multibyte flag of the current buffer.
457 Only do this if the default value of `enable-multibyte-characters' is
458 non-nil.  This is a no-op in XEmacs."
459         (set-buffer-multibyte 'to))
460     (defalias 'mm-enable-multibyte 'ignore))
461
462   (if mm-emacs-mule
463       (defun mm-disable-multibyte ()
464         "Unset the multibyte flag of in the current buffer.
465 This is a no-op in XEmacs."
466         (set-buffer-multibyte nil))
467     (defalias 'mm-disable-multibyte 'ignore)))
468
469 (defun mm-preferred-coding-system (charset)
470   ;; A typo in some Emacs versions.
471   (or (get-charset-property charset 'preferred-coding-system)
472       (get-charset-property charset 'prefered-coding-system)))
473
474 ;; Mule charsets shouldn't be used.
475 (defsubst mm-guess-charset ()
476   "Guess Mule charset from the language environment."
477   (or
478    mail-parse-mule-charset ;; cached mule-charset
479    (progn
480      (setq mail-parse-mule-charset
481            (and (boundp 'current-language-environment)
482                 (car (last
483                       (assq 'charset
484                             (assoc current-language-environment
485                                    language-info-alist))))))
486      (if (or (not mail-parse-mule-charset)
487              (eq mail-parse-mule-charset 'ascii))
488          (setq mail-parse-mule-charset
489                (or (car (last (assq mail-parse-charset
490                                     mm-mime-mule-charset-alist)))
491                    ;; default
492                    'latin-iso8859-1)))
493      mail-parse-mule-charset)))
494
495 (defun mm-charset-after (&optional pos)
496   "Return charset of a character in current buffer at position POS.
497 If POS is nil, it defauls to the current point.
498 If POS is out of range, the value is nil.
499 If the charset is `composition', return the actual one."
500   (let ((char (char-after pos)) charset)
501     (if (< (mm-char-int char) 128)
502         (setq charset 'ascii)
503       ;; charset-after is fake in some Emacsen.
504       (setq charset (and (fboundp 'char-charset) (char-charset char)))
505       (if (eq charset 'composition)     ; Mule 4
506           (let ((p (or pos (point))))
507             (cadr (find-charset-region p (1+ p))))
508         (if (and charset (not (memq charset '(ascii eight-bit-control
509                                                     eight-bit-graphic))))
510             charset
511           (mm-guess-charset))))))
512
513 (defun mm-mime-charset (charset)
514   "Return the MIME charset corresponding to the given Mule CHARSET."
515   (if (eq charset 'unknown)
516       (error "The message contains non-printable characters, please use attachment"))
517   (if (and (fboundp 'coding-system-get) (fboundp 'get-charset-property))
518       ;; This exists in Emacs 20.
519       (or
520        (and (mm-preferred-coding-system charset)
521             (or (coding-system-get
522                  (mm-preferred-coding-system charset) :mime-charset)
523                 (coding-system-get
524                  (mm-preferred-coding-system charset) 'mime-charset)))
525        (and (eq charset 'ascii)
526             'us-ascii)
527        (mm-preferred-coding-system charset)
528        (mm-mule-charset-to-mime-charset charset))
529     ;; This is for XEmacs.
530     (mm-mule-charset-to-mime-charset charset)))
531
532 (defun mm-delete-duplicates (list)
533   "Simple substitute for CL `delete-duplicates', testing with `equal'."
534   (let (result head)
535     (while list
536       (setq head (car list))
537       (setq list (delete head list))
538       (setq result (cons head result)))
539     (nreverse result)))
540
541 ;; Fixme:  This is used in places when it should be testing the
542 ;; default multibyteness.  See mm-default-multibyte-p.
543 (eval-and-compile
544   (if (and (not (featurep 'xemacs))
545            (boundp 'enable-multibyte-characters))
546       (defun mm-multibyte-p ()
547         "Non-nil if multibyte is enabled in the current buffer."
548         enable-multibyte-characters)
549     (defun mm-multibyte-p () (featurep 'mule))))
550
551 (defun mm-default-multibyte-p ()
552   "Return non-nil if the session is multibyte.
553 This affects whether coding conversion should be attempted generally."
554   (if (featurep 'mule)
555       (if (boundp 'default-enable-multibyte-characters)
556           default-enable-multibyte-characters
557         t)))
558
559 (defun mm-iso-8859-x-to-15-region (&optional b e)
560   (if (fboundp 'char-charset)
561       (let (charset item c inconvertible)
562         (save-restriction
563           (if e (narrow-to-region b e))
564           (goto-char (point-min))
565           (skip-chars-forward "\0-\177")
566           (while (not (eobp))
567             (cond
568              ((not (setq item (assq (char-charset (setq c (char-after)))
569                                     mm-iso-8859-x-to-15-table)))
570               (forward-char))
571              ((memq c (cdr (cdr item)))
572               (setq inconvertible t)
573               (forward-char))
574              (t
575               (insert-before-markers (prog1 (+ c (car (cdr item)))
576                                        (delete-char 1)))))
577             (skip-chars-forward "\0-\177")))
578         (not inconvertible))))
579
580 (defun mm-sort-coding-systems-predicate (a b)
581   (let ((priorities
582          (mapcar (lambda (cs)
583                    ;; Note: invalid entries are dropped silently
584                    (and (setq cs (mm-coding-system-p cs))
585                         (coding-system-base cs)))
586                  mm-coding-system-priorities)))
587     (and (setq a (mm-coding-system-p a))
588          (if (setq b (mm-coding-system-p b))
589              (> (length (memq (coding-system-base a) priorities))
590                 (length (memq (coding-system-base b) priorities)))
591            t))))
592
593 (eval-when-compile
594   (autoload 'latin-unity-massage-name "latin-unity")
595   (autoload 'latin-unity-maybe-remap "latin-unity")
596   (autoload 'latin-unity-representations-feasible-region "latin-unity")
597   (autoload 'latin-unity-representations-present-region "latin-unity")
598   (defvar latin-unity-coding-systems)
599   (defvar latin-unity-ucs-list))
600
601 (defun mm-xemacs-find-mime-charset-1 (begin end)
602   "Determine which MIME charset to use to send region as message.
603 This uses the XEmacs-specific latin-unity package to better handle the
604 case where identical characters from diverse ISO-8859-? character sets
605 can be encoded using a single one of the corresponding coding systems.
606
607 It treats `mm-coding-system-priorities' as the list of preferred
608 coding systems; a useful example setting for this list in Western
609 Europe would be '(iso-8859-1 iso-8859-15 utf-8), which would default
610 to the very standard Latin 1 coding system, and only move to coding
611 systems that are less supported as is necessary to encode the
612 characters that exist in the buffer.
613
614 Latin Unity doesn't know about those non-ASCII Roman characters that
615 are available in various East Asian character sets.  As such, its
616 behavior if you have a JIS 0212 LATIN SMALL LETTER A WITH ACUTE in a
617 buffer and it can otherwise be encoded as Latin 1, won't be ideal.
618 But this is very much a corner case, so don't worry about it."
619   (let ((systems mm-coding-system-priorities) csets psets curset)
620
621     ;; Load the Latin Unity library, if available.
622     (when (and (not (featurep 'latin-unity)) (locate-library "latin-unity"))
623       (require 'latin-unity))
624
625     ;; Now, can we use it?
626     (if (featurep 'latin-unity)
627         (progn
628           (setq csets (latin-unity-representations-feasible-region begin end)
629                 psets (latin-unity-representations-present-region begin end))
630
631           (catch 'done
632
633             ;; Pass back the first coding system in the preferred list
634             ;; that can encode the whole region.
635             (dolist (curset systems)
636               (setq curset (latin-unity-massage-name 'buffer-default curset))
637
638               ;; If the coding system is a universal coding system, then
639               ;; it can certainly encode all the characters in the region.
640               (if (memq curset latin-unity-ucs-list)
641                   (throw 'done (list curset)))
642
643               ;; If a coding system isn't universal, and isn't in
644               ;; the list that latin unity knows about, we can't
645               ;; decide whether to use it here. Leave that until later
646               ;; in `mm-find-mime-charset-region' function, whence we
647               ;; have been called.
648               (unless (memq curset latin-unity-coding-systems)
649                 (throw 'done nil))
650
651               ;; Right, we know about this coding system, and it may
652               ;; conceivably be able to encode all the characters in
653               ;; the region.
654               (if (latin-unity-maybe-remap begin end curset csets psets t)
655                   (throw 'done (list curset))))
656
657             ;; Can't encode using anything from the
658             ;; `mm-coding-system-priorities' list.
659             ;; Leave `mm-find-mime-charset' to do most of the work.
660             nil))
661
662       ;; Right, latin unity isn't available; let `mm-find-charset-region'
663       ;; take its default action, which equally applies to GNU Emacs.
664       nil)))
665
666 (defmacro mm-xemacs-find-mime-charset (begin end)
667   (when (featurep 'xemacs)
668     `(mm-xemacs-find-mime-charset-1 ,begin ,end)))
669
670 (defun mm-find-mime-charset-region (b e &optional hack-charsets)
671   "Return the MIME charsets needed to encode the region between B and E.
672 nil means ASCII, a single-element list represents an appropriate MIME
673 charset, and a longer list means no appropriate charset."
674   (let (charsets)
675     ;; The return possibilities of this function are a mess...
676     (or (and (mm-multibyte-p)
677              mm-use-find-coding-systems-region
678              ;; Find the mime-charset of the most preferred coding
679              ;; system that has one.
680              (let ((systems (find-coding-systems-region b e)))
681                (when mm-coding-system-priorities
682                  (setq systems
683                        (sort systems 'mm-sort-coding-systems-predicate)))
684                (setq systems (delq 'compound-text systems))
685                (unless (equal systems '(undecided))
686                  (while systems
687                    (let* ((head (pop systems))
688                           (cs (or (coding-system-get head :mime-charset)
689                                   (coding-system-get head 'mime-charset))))
690                      ;; The mime-charset (`x-ctext') of
691                      ;; `compound-text' is not in the IANA list.  We
692                      ;; shouldn't normally use anything here with a
693                      ;; mime-charset having an `x-' prefix.
694                      ;; Fixme:  Allow this to be overridden, since
695                      ;; there is existing use of x-ctext.
696                      ;; Also people apparently need the coding system
697                      ;; `iso-2022-jp-3' (which Mule-UCS defines with
698                      ;; mime-charset, though it's not valid).
699                      (if (and cs
700                               (not (string-match "^[Xx]-" (symbol-name cs)))
701                               ;; UTF-16 of any variety is invalid for
702                               ;; text parts and, unfortunately, has
703                               ;; mime-charset defined both in Mule-UCS
704                               ;; and versions of Emacs.  (The name
705                               ;; might be `mule-utf-16...'  or
706                               ;; `utf-16...'.)
707                               (not (string-match "utf-16" (symbol-name cs))))
708                          (setq systems nil
709                                charsets (list cs))))))
710                charsets))
711         ;; If we're XEmacs, and some coding system is appropriate,
712         ;; mm-xemacs-find-mime-charset will return an appropriate list.
713         ;; Otherwise, we'll get nil, and the next setq will get invoked.
714         (setq charsets (mm-xemacs-find-mime-charset b e))
715
716         ;; We're not multibyte, or a single coding system won't cover it.
717         (setq charsets
718               (mm-delete-duplicates
719                (mapcar 'mm-mime-charset
720                        (delq 'ascii
721                              (mm-find-charset-region b e))))))
722     (if (and (> (length charsets) 1)
723              (memq 'iso-8859-15 charsets)
724              (memq 'iso-8859-15 hack-charsets)
725              (save-excursion (mm-iso-8859-x-to-15-region b e)))
726         (mapcar (lambda (x) (setq charsets (delq (car x) charsets)))
727                 mm-iso-8859-15-compatible))
728     (if (and (memq 'iso-2022-jp-2 charsets)
729              (memq 'iso-2022-jp-2 hack-charsets))
730         (setq charsets (delq 'iso-2022-jp charsets)))
731     charsets))
732
733 (defmacro mm-with-unibyte-buffer (&rest forms)
734   "Create a temporary buffer, and evaluate FORMS there like `progn'.
735 Use unibyte mode for this."
736   `(let (default-enable-multibyte-characters)
737      (with-temp-buffer ,@forms)))
738 (put 'mm-with-unibyte-buffer 'lisp-indent-function 0)
739 (put 'mm-with-unibyte-buffer 'edebug-form-spec '(body))
740
741 (defmacro mm-with-multibyte-buffer (&rest forms)
742   "Create a temporary buffer, and evaluate FORMS there like `progn'.
743 Use multibyte mode for this."
744   `(let ((default-enable-multibyte-characters t))
745      (with-temp-buffer ,@forms)))
746 (put 'mm-with-multibyte-buffer 'lisp-indent-function 0)
747 (put 'mm-with-multibyte-buffer 'edebug-form-spec '(body))
748
749 (defmacro mm-with-unibyte-current-buffer (&rest forms)
750   "Evaluate FORMS with current buffer temporarily made unibyte.
751 Also bind `default-enable-multibyte-characters' to nil.
752 Equivalent to `progn' in XEmacs"
753   (let ((multibyte (make-symbol "multibyte"))
754         (buffer (make-symbol "buffer")))
755     `(if mm-emacs-mule
756          (let ((,multibyte enable-multibyte-characters)
757                (,buffer (current-buffer)))
758            (unwind-protect
759                (let (default-enable-multibyte-characters)
760                  (set-buffer-multibyte nil)
761                  ,@forms)
762              (set-buffer ,buffer)
763              (set-buffer-multibyte ,multibyte)))
764        (let (default-enable-multibyte-characters)
765          ,@forms))))
766 (put 'mm-with-unibyte-current-buffer 'lisp-indent-function 0)
767 (put 'mm-with-unibyte-current-buffer 'edebug-form-spec '(body))
768
769 (defmacro mm-with-unibyte (&rest forms)
770   "Eval the FORMS with the default value of `enable-multibyte-characters' nil."
771   `(let (default-enable-multibyte-characters)
772      ,@forms))
773 (put 'mm-with-unibyte 'lisp-indent-function 0)
774 (put 'mm-with-unibyte 'edebug-form-spec '(body))
775
776 (defmacro mm-with-multibyte (&rest forms)
777   "Eval the FORMS with the default value of `enable-multibyte-characters' t."
778   `(let ((default-enable-multibyte-characters t))
779      ,@forms))
780 (put 'mm-with-multibyte 'lisp-indent-function 0)
781 (put 'mm-with-multibyte 'edebug-form-spec '(body))
782
783 (defun mm-find-charset-region (b e)
784   "Return a list of Emacs charsets in the region B to E."
785   (cond
786    ((and (mm-multibyte-p)
787          (fboundp 'find-charset-region))
788     ;; Remove composition since the base charsets have been included.
789     ;; Remove eight-bit-*, treat them as ascii.
790     (let ((css (find-charset-region b e)))
791       (mapcar (lambda (cs) (setq css (delq cs css)))
792               '(composition eight-bit-control eight-bit-graphic
793                             control-1))
794       css))
795    (t
796     ;; We are in a unibyte buffer or XEmacs non-mule, so we futz around a bit.
797     (save-excursion
798       (save-restriction
799         (narrow-to-region b e)
800         (goto-char (point-min))
801         (skip-chars-forward "\0-\177")
802         (if (eobp)
803             '(ascii)
804           (let (charset)
805             (setq charset
806                   (and (boundp 'current-language-environment)
807                        (car (last (assq 'charset
808                                         (assoc current-language-environment
809                                                language-info-alist))))))
810             (if (eq charset 'ascii) (setq charset nil))
811             (or charset
812                 (setq charset
813                       (car (last (assq mail-parse-charset
814                                        mm-mime-mule-charset-alist)))))
815             (list 'ascii (or charset 'latin-iso8859-1)))))))))
816
817 (defun mm-auto-mode-alist ()
818   "Return an `auto-mode-alist' with only the .gz (etc) thingies."
819   (let ((alist auto-mode-alist)
820         out)
821     (while alist
822       (when (listp (cdar alist))
823         (push (car alist) out))
824       (pop alist))
825     (nreverse out)))
826
827 (defvar mm-inhibit-file-name-handlers
828   '(jka-compr-handler image-file-handler)
829   "A list of handlers doing (un)compression (etc) thingies.")
830
831 (defun mm-insert-file-contents (filename &optional visit beg end replace
832                                          inhibit)
833   "Like `insert-file-contents', but only reads in the file.
834 A buffer may be modified in several ways after reading into the buffer due
835 to advanced Emacs features, such as file-name-handlers, format decoding,
836 `find-file-hooks', etc.
837 If INHIBIT is non-nil, inhibit `mm-inhibit-file-name-handlers'.
838   This function ensures that none of these modifications will take place."
839   (let ((format-alist nil)
840         (auto-mode-alist (if inhibit nil (mm-auto-mode-alist)))
841         (default-major-mode 'fundamental-mode)
842         (enable-local-variables nil)
843         (after-insert-file-functions nil)
844         (enable-local-eval nil)
845         (find-file-hooks nil)
846         (inhibit-file-name-operation (if inhibit
847                                          'insert-file-contents
848                                        inhibit-file-name-operation))
849         (inhibit-file-name-handlers
850          (if inhibit
851              (append mm-inhibit-file-name-handlers
852                      inhibit-file-name-handlers)
853            inhibit-file-name-handlers)))
854     (insert-file-contents filename visit beg end replace)))
855
856 (defun mm-append-to-file (start end filename &optional codesys inhibit)
857   "Append the contents of the region to the end of file FILENAME.
858 When called from a function, expects three arguments,
859 START, END and FILENAME.  START and END are buffer positions
860 saying what text to write.
861 Optional fourth argument specifies the coding system to use when
862 encoding the file.
863 If INHIBIT is non-nil, inhibit `mm-inhibit-file-name-handlers'."
864   (let ((coding-system-for-write
865          (or codesys mm-text-coding-system-for-write
866              mm-text-coding-system))
867         (inhibit-file-name-operation (if inhibit
868                                          'append-to-file
869                                        inhibit-file-name-operation))
870         (inhibit-file-name-handlers
871          (if inhibit
872              (append mm-inhibit-file-name-handlers
873                      inhibit-file-name-handlers)
874            inhibit-file-name-handlers)))
875     (write-region start end filename t 'no-message)
876     (message "Appended to %s" filename)))
877
878 (defun mm-write-region (start end filename &optional append visit lockname
879                               coding-system inhibit)
880
881   "Like `write-region'.
882 If INHIBIT is non-nil, inhibit `mm-inhibit-file-name-handlers'."
883   (let ((coding-system-for-write
884          (or coding-system mm-text-coding-system-for-write
885              mm-text-coding-system))
886         (inhibit-file-name-operation (if inhibit
887                                          'write-region
888                                        inhibit-file-name-operation))
889         (inhibit-file-name-handlers
890          (if inhibit
891              (append mm-inhibit-file-name-handlers
892                      inhibit-file-name-handlers)
893            inhibit-file-name-handlers)))
894     (write-region start end filename append visit lockname)))
895
896 (defun mm-image-load-path (&optional package)
897   (let (dir result)
898     (dolist (path load-path (nreverse result))
899       (when (and path
900                  (file-directory-p
901                   (setq dir (concat (file-name-directory
902                                      (directory-file-name path))
903                                     "etc/images/" (or package "gnus/")))))
904         (push dir result))
905       (push path result))))
906
907 ;; Fixme: This doesn't look useful where it's used.
908 (if (fboundp 'detect-coding-region)
909     (defun mm-detect-coding-region (start end)
910       "Like `detect-coding-region' except returning the best one."
911       (let ((coding-systems
912              (detect-coding-region (point) (point-max))))
913         (or (car-safe coding-systems)
914             coding-systems)))
915   (defun mm-detect-coding-region (start end)
916     (let ((point (point)))
917       (goto-char start)
918       (skip-chars-forward "\0-\177" end)
919       (prog1
920           (if (eq (point) end) 'ascii (mm-guess-charset))
921         (goto-char point)))))
922
923 (if (fboundp 'coding-system-get)
924     (defun mm-detect-mime-charset-region (start end)
925       "Detect MIME charset of the text in the region between START and END."
926       (let ((cs (mm-detect-coding-region start end)))
927         (or (coding-system-get cs :mime-charset)
928             (coding-system-get cs 'mime-charset))))
929   (defun mm-detect-mime-charset-region (start end)
930     "Detect MIME charset of the text in the region between START and END."
931     (let ((cs (mm-detect-coding-region start end)))
932       cs)))
933
934 (eval-when-compile
935   (unless (fboundp 'coding-system-to-mime-charset)
936     (defalias 'coding-system-to-mime-charset 'ignore)))
937
938 (defun mm-coding-system-to-mime-charset (coding-system)
939   "Return the MIME charset corresponding to CODING-SYSTEM.
940 To make this function work with XEmacs, the APEL package is required."
941   (when coding-system
942     (or (and (fboundp 'coding-system-get)
943              (or (coding-system-get coding-system :mime-charset)
944                  (coding-system-get coding-system 'mime-charset)))
945         (and (featurep 'xemacs)
946              (or (and (fboundp 'coding-system-to-mime-charset)
947                       (not (eq (symbol-function 'coding-system-to-mime-charset)
948                                'ignore)))
949                  (and (condition-case nil
950                           (require 'mcharset)
951                         (error nil))
952                       (fboundp 'coding-system-to-mime-charset)))
953              (coding-system-to-mime-charset coding-system)))))
954
955 (eval-when-compile
956   (require 'jka-compr))
957
958 (defun mm-decompress-buffer (filename &optional inplace force)
959   "Decompress buffer's contents, depending on jka-compr.
960 Only when FORCE is t or `auto-compression-mode' is enabled and FILENAME
961 agrees with `jka-compr-compression-info-list', decompression is done.
962 Signal an error if FORCE is neither nil nor t and compressed data are
963 not decompressed because `auto-compression-mode' is disabled.
964 If INPLACE is nil, return decompressed data or nil without modifying
965 the buffer.  Otherwise, replace the buffer's contents with the
966 decompressed data.  The buffer's multibyteness must be turned off."
967   (when (and filename
968              (if force
969                  (prog1 t (require 'jka-compr))
970                (and (fboundp 'jka-compr-installed-p)
971                     (jka-compr-installed-p))))
972     (let ((info (jka-compr-get-compression-info filename)))
973       (when info
974         (unless (or (memq force (list nil t))
975                     (jka-compr-installed-p))
976           (error ""))
977         (let ((prog (jka-compr-info-uncompress-program info))
978               (args (jka-compr-info-uncompress-args info))
979               (msg (format "%s %s..."
980                            (jka-compr-info-uncompress-message info)
981                            filename))
982               (err-file (jka-compr-make-temp-name))
983               (cur (current-buffer))
984               (coding-system-for-read mm-binary-coding-system)
985               (coding-system-for-write mm-binary-coding-system)
986               retval err-msg)
987           (message "%s" msg)
988           (with-temp-buffer
989             (insert-buffer-substring cur)
990             (condition-case err
991                 (progn
992                   (unless (memq (apply 'call-process-region
993                                        (point-min) (point-max)
994                                        prog t (list t err-file) nil args)
995                                 jka-compr-acceptable-retval-list)
996                     (erase-buffer)
997                     (insert (mapconcat
998                              'identity
999                              (delete "" (split-string
1000                                          (prog2
1001                                              (insert-file-contents err-file)
1002                                              (buffer-string)
1003                                            (erase-buffer))))
1004                              " ")
1005                             "\n")
1006                     (setq err-msg
1007                           (format "Error while executing \"%s %s < %s\""
1008                                   prog (mapconcat 'identity args " ")
1009                                   filename)))
1010                   (setq retval (buffer-string)))
1011               (error
1012                (setq err-msg (error-message-string err)))))
1013           (when (file-exists-p err-file)
1014             (ignore-errors (jka-compr-delete-temp-file err-file)))
1015           (when inplace
1016             (unless err-msg
1017               (delete-region (point-min) (point-max))
1018               (insert retval))
1019             (setq retval nil))
1020           (message "%s" (or err-msg (concat msg "done")))
1021           retval)))))
1022
1023 (eval-when-compile
1024   (unless (fboundp 'coding-system-name)
1025     (defalias 'coding-system-name 'ignore))
1026   (unless (fboundp 'find-file-coding-system-for-read-from-filename)
1027     (defalias 'find-file-coding-system-for-read-from-filename 'ignore))
1028   (unless (fboundp 'find-operation-coding-system)
1029     (defalias 'find-operation-coding-system 'ignore)))
1030
1031 (defun mm-find-buffer-file-coding-system (&optional filename)
1032   "Find coding system used to decode the contents of the current buffer.
1033 This function looks for the coding system magic cookie or examines the
1034 coding system specified by `file-coding-system-alist' being associated
1035 with FILENAME which defaults to `buffer-file-name'.  Data compressed by
1036 gzip, bzip2, etc. are allowed."
1037   (unless filename
1038     (setq filename buffer-file-name))
1039   (save-excursion
1040     (let ((decomp (mm-decompress-buffer filename nil t)))
1041       (when decomp
1042         (set-buffer (let (default-enable-multibyte-characters)
1043                       (generate-new-buffer " *temp*")))
1044         (insert decomp)
1045         (setq filename (file-name-sans-extension filename)))
1046       (goto-char (point-min))
1047       (prog1
1048           (cond
1049            ((boundp 'set-auto-coding-function) ;; Emacs
1050             (if filename
1051                 (or (funcall (symbol-value 'set-auto-coding-function)
1052                              filename (- (point-max) (point-min)))
1053                     (car (find-operation-coding-system 'insert-file-contents
1054                                                        filename)))
1055               (let (auto-coding-alist)
1056                 (condition-case nil
1057                     (funcall (symbol-value 'set-auto-coding-function)
1058                              nil (- (point-max) (point-min)))
1059                   (error nil)))))
1060            ((featurep 'file-coding) ;; XEmacs
1061             (let ((case-fold-search t)
1062                   (end (point-at-eol))
1063                   codesys start)
1064               (or
1065                (and (re-search-forward "-\\*-+[\t ]*" end t)
1066                     (progn
1067                       (setq start (match-end 0))
1068                       (re-search-forward "[\t ]*-+\\*-" end t))
1069                     (progn
1070                       (setq end (match-beginning 0))
1071                       (goto-char start)
1072                       (or (looking-at "coding:[\t ]*\\([^\t ;]+\\)")
1073                           (re-search-forward
1074                            "[\t ;]+coding:[\t ]*\\([^\t ;]+\\)"
1075                            end t)))
1076                     (find-coding-system (setq codesys
1077                                               (intern (match-string 1))))
1078                     codesys)
1079                (and (re-search-forward "^[\t ]*;+[\t ]*Local[\t ]+Variables:"
1080                                        nil t)
1081                     (progn
1082                       (setq start (match-end 0))
1083                       (re-search-forward "^[\t ]*;+[\t ]*End:" nil t))
1084                     (progn
1085                       (setq end (match-beginning 0))
1086                       (goto-char start)
1087                       (re-search-forward
1088                        "^[\t ]*;+[\t ]*coding:[\t ]*\\([^\t\n\r ]+\\)"
1089                        end t))
1090                     (find-coding-system (setq codesys
1091                                               (intern (match-string 1))))
1092                     codesys)
1093                (and (progn
1094                       (goto-char (point-min))
1095                       (setq case-fold-search nil)
1096                       (re-search-forward "^;;;coding system: "
1097                                          ;;(+ (point-min) 3000) t))
1098                                          nil t))
1099                     (looking-at "[^\t\n\r ]+")
1100                     (find-coding-system
1101                      (setq codesys (intern (match-string 0))))
1102                     codesys)
1103                (and filename
1104                     (setq codesys
1105                           (find-file-coding-system-for-read-from-filename
1106                            filename))
1107                     (coding-system-name (coding-system-base codesys)))))))
1108         (when decomp
1109           (kill-buffer (current-buffer)))))))
1110
1111 (provide 'mm-util)
1112
1113 ;;; arch-tag: 94dc5388-825d-4fd1-bfa5-2100aa351238
1114 ;;; mm-util.el ends here