9c7e0f68d379f22d330c5b5bf80bd7e4b521ba45
[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
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      (multibyte-string-p . ignore)
77      ;; It is not a MIME function, but some MIME functions use it.
78      (make-temp-file . (lambda (prefix &optional dir-flag)
79                          (let ((file (expand-file-name
80                                       (make-temp-name prefix)
81                                       (if (fboundp 'temp-directory)
82                                           (temp-directory)
83                                         temporary-file-directory))))
84                            (if dir-flag
85                                (make-directory file))
86                            file)))
87      (insert-byte . insert-char)
88      (multibyte-char-to-unibyte . identity))))
89
90 (eval-and-compile
91   (defalias 'mm-char-or-char-int-p
92     (cond
93      ((fboundp 'char-or-char-int-p) 'char-or-char-int-p)
94      ((fboundp 'char-valid-p) 'char-valid-p)
95      (t 'identity))))
96
97 ;; Fixme:  This seems always to be used to read a MIME charset, so it
98 ;; should be re-named and fixed (in Emacs) to offer completion only on
99 ;; proper charset names (base coding systems which have a
100 ;; mime-charset defined).  XEmacs doesn't believe in mime-charset;
101 ;; test with
102 ;;   `(or (coding-system-get 'iso-8859-1 'mime-charset)
103 ;;        (coding-system-get 'iso-8859-1 :mime-charset))'
104 ;; Actually, there should be an `mm-coding-system-mime-charset'.
105 (eval-and-compile
106   (defalias 'mm-read-coding-system
107     (cond
108      ((fboundp 'read-coding-system)
109       (if (and (featurep 'xemacs)
110                (<= (string-to-number emacs-version) 21.1))
111           (lambda (prompt &optional default-coding-system)
112             (read-coding-system prompt))
113         'read-coding-system))
114      (t (lambda (prompt &optional default-coding-system)
115           "Prompt the user for a coding system."
116           (completing-read
117            prompt (mapcar (lambda (s) (list (symbol-name (car s))))
118                           mm-mime-mule-charset-alist)))))))
119
120 (defvar mm-coding-system-list nil)
121 (defun mm-get-coding-system-list ()
122   "Get the coding system list."
123   (or mm-coding-system-list
124       (setq mm-coding-system-list (mm-coding-system-list))))
125
126 (defun mm-coding-system-p (cs)
127   "Return non-nil if CS is a symbol naming a coding system.
128 In XEmacs, also return non-nil if CS is a coding system object."
129   (if (fboundp 'find-coding-system)
130       (find-coding-system cs)
131     (if (fboundp 'coding-system-p)
132         (coding-system-p cs)
133       ;; Is this branch ever actually useful?
134       (memq cs (mm-get-coding-system-list)))))
135
136 (defvar mm-charset-synonym-alist
137   `(
138     ;; Not in XEmacs, but it's not a proper MIME charset anyhow.
139     ,@(unless (mm-coding-system-p 'x-ctext)
140        '((x-ctext . ctext)))
141     ;; ISO-8859-15 is very similar to ISO-8859-1.  But it's _different_!
142     ,@(unless (mm-coding-system-p 'iso-8859-15)
143        '((iso-8859-15 . iso-8859-1)))
144     ;; BIG-5HKSCS is similar to, but different than, BIG-5.
145     ,@(unless (mm-coding-system-p 'big5-hkscs)
146         '((big5-hkscs . big5)))
147     ;; Windows-1252 is actually a superset of Latin-1.  See also
148     ;; `gnus-article-dumbquotes-map'.
149     ,@(unless (mm-coding-system-p 'windows-1252)
150        (if (mm-coding-system-p 'cp1252)
151            '((windows-1252 . cp1252))
152          '((windows-1252 . iso-8859-1))))
153     ;; Windows-1250 is a variant of Latin-2 heavily used by Microsoft
154     ;; Outlook users in Czech republic. Use this to allow reading of their
155     ;; e-mails. cp1250 should be defined by M-x codepage-setup.
156     ,@(if (and (not (mm-coding-system-p 'windows-1250))
157                (mm-coding-system-p 'cp1250))
158           '((windows-1250 . cp1250)))
159     ;; A Microsoft misunderstanding.
160     ,@(unless (mm-coding-system-p 'ks_c_5601-1987)
161         (if (mm-coding-system-p 'cp949)
162             '((ks_c_5601-1987 . cp949))
163           '((ks_c_5601-1987 . euc-kr))))
164     )
165   "A mapping from invalid charset names to the real charset names.")
166
167 (defvar mm-binary-coding-system
168   (cond
169    ((mm-coding-system-p 'binary) 'binary)
170    ((mm-coding-system-p 'no-conversion) 'no-conversion)
171    (t nil))
172   "100% binary coding system.")
173
174 (defvar mm-text-coding-system
175   (or (if (memq system-type '(windows-nt ms-dos ms-windows))
176           (and (mm-coding-system-p 'raw-text-dos) 'raw-text-dos)
177         (and (mm-coding-system-p 'raw-text) 'raw-text))
178       mm-binary-coding-system)
179   "Text-safe coding system (For removing ^M).")
180
181 (defvar mm-text-coding-system-for-write nil
182   "Text coding system for write.")
183
184 (defvar mm-auto-save-coding-system
185   (cond
186    ((mm-coding-system-p 'utf-8-emacs)   ; Mule 7
187     (if (memq system-type '(windows-nt ms-dos ms-windows))
188         (if (mm-coding-system-p 'utf-8-emacs-dos)
189             'utf-8-emacs-dos mm-binary-coding-system)
190       'utf-8-emacs))
191    ((mm-coding-system-p 'emacs-mule)
192     (if (memq system-type '(windows-nt ms-dos ms-windows))
193         (if (mm-coding-system-p 'emacs-mule-dos)
194             'emacs-mule-dos mm-binary-coding-system)
195       'emacs-mule))
196    ((mm-coding-system-p 'escape-quoted) 'escape-quoted)
197    (t mm-binary-coding-system))
198   "Coding system of auto save file.")
199
200 (defvar mm-universal-coding-system mm-auto-save-coding-system
201   "The universal coding system.")
202
203 ;; Fixme: some of the cars here aren't valid MIME charsets.  That
204 ;; should only matter with XEmacs, though.
205 (defvar mm-mime-mule-charset-alist
206   `((us-ascii ascii)
207     (iso-8859-1 latin-iso8859-1)
208     (iso-8859-2 latin-iso8859-2)
209     (iso-8859-3 latin-iso8859-3)
210     (iso-8859-4 latin-iso8859-4)
211     (iso-8859-5 cyrillic-iso8859-5)
212     ;; Non-mule (X)Emacs uses the last mule-charset for 8bit characters.
213     ;; The fake mule-charset, gnus-koi8-r, tells Gnus that the default
214     ;; charset is koi8-r, not iso-8859-5.
215     (koi8-r cyrillic-iso8859-5 gnus-koi8-r)
216     (iso-8859-6 arabic-iso8859-6)
217     (iso-8859-7 greek-iso8859-7)
218     (iso-8859-8 hebrew-iso8859-8)
219     (iso-8859-9 latin-iso8859-9)
220     (iso-8859-14 latin-iso8859-14)
221     (iso-8859-15 latin-iso8859-15)
222     (viscii vietnamese-viscii-lower)
223     (iso-2022-jp latin-jisx0201 japanese-jisx0208 japanese-jisx0208-1978)
224     (euc-kr korean-ksc5601)
225     (gb2312 chinese-gb2312)
226     (big5 chinese-big5-1 chinese-big5-2)
227     (tibetan tibetan)
228     (thai-tis620 thai-tis620)
229     (iso-2022-7bit ethiopic arabic-1-column arabic-2-column)
230     (iso-2022-jp-2 latin-iso8859-1 greek-iso8859-7
231                    latin-jisx0201 japanese-jisx0208-1978
232                    chinese-gb2312 japanese-jisx0208
233                    korean-ksc5601 japanese-jisx0212
234                    katakana-jisx0201)
235     (iso-2022-int-1 latin-iso8859-1 greek-iso8859-7
236                     latin-jisx0201 japanese-jisx0208-1978
237                     chinese-gb2312 japanese-jisx0208
238                     korean-ksc5601 japanese-jisx0212
239                     chinese-cns11643-1 chinese-cns11643-2)
240     (iso-2022-int-1 latin-iso8859-1 latin-iso8859-2
241                     cyrillic-iso8859-5 greek-iso8859-7
242                     latin-jisx0201 japanese-jisx0208-1978
243                     chinese-gb2312 japanese-jisx0208
244                     korean-ksc5601 japanese-jisx0212
245                     chinese-cns11643-1 chinese-cns11643-2
246                     chinese-cns11643-3 chinese-cns11643-4
247                     chinese-cns11643-5 chinese-cns11643-6
248                     chinese-cns11643-7)
249     ,(if (or (not (fboundp 'charsetp)) ;; non-Mule case
250              (charsetp 'unicode-a)
251              (not (mm-coding-system-p 'mule-utf-8)))
252          '(utf-8 unicode-a unicode-b unicode-c unicode-d unicode-e)
253        ;; If we have utf-8 we're in Mule 5+.
254        (append '(utf-8)
255                (delete 'ascii
256                        (coding-system-get 'mule-utf-8 'safe-charsets)))))
257   "Alist of MIME-charset/MULE-charsets.")
258
259 ;; Correct by construction, but should be unnecessary:
260 ;; XEmacs hates it.
261 (when (and (not (featurep 'xemacs))
262            (fboundp 'coding-system-list)
263            (fboundp 'sort-coding-systems))
264   (setq mm-mime-mule-charset-alist
265         (apply
266          'nconc
267          (mapcar
268           (lambda (cs)
269             (when (and (or (coding-system-get cs :mime-charset) ; Emacs 22
270                            (coding-system-get cs 'mime-charset))
271                        (not (eq t (coding-system-get cs 'safe-charsets))))
272               (list (cons (or (coding-system-get cs :mime-charset)
273                               (coding-system-get cs 'mime-charset))
274                           (delq 'ascii
275                                 (coding-system-get cs 'safe-charsets))))))
276           (sort-coding-systems (coding-system-list 'base-only))))))
277
278 (defvar mm-hack-charsets '(iso-8859-15 iso-2022-jp-2)
279   "A list of special charsets.
280 Valid elements include:
281 `iso-8859-15'    convert ISO-8859-1, -9 to ISO-8859-15 if ISO-8859-15 exists.
282 `iso-2022-jp-2'  convert ISO-2022-jp to ISO-2022-jp-2 if ISO-2022-jp-2 exists."
283 )
284
285 (defvar mm-iso-8859-15-compatible
286   '((iso-8859-1 "\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE")
287     (iso-8859-9 "\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE\xD0\xDD\xDE\xF0\xFD\xFE"))
288   "ISO-8859-15 exchangeable coding systems and inconvertible characters.")
289
290 (defvar mm-iso-8859-x-to-15-table
291   (and (fboundp 'coding-system-p)
292        (mm-coding-system-p 'iso-8859-15)
293        (mapcar
294         (lambda (cs)
295           (if (mm-coding-system-p (car cs))
296               (let ((c (string-to-char
297                         (decode-coding-string "\341" (car cs)))))
298                 (cons (char-charset c)
299                       (cons
300                        (- (string-to-char
301                            (decode-coding-string "\341" 'iso-8859-15)) c)
302                        (string-to-list (decode-coding-string (car (cdr cs))
303                                                              (car cs))))))
304             '(gnus-charset 0)))
305         mm-iso-8859-15-compatible))
306   "A table of the difference character between ISO-8859-X and ISO-8859-15.")
307
308 (defcustom mm-coding-system-priorities
309   (if (boundp 'current-language-environment)
310       (let ((lang (symbol-value 'current-language-environment)))
311         (cond ((string= lang "Japanese")
312                ;; Japanese users may prefer iso-2022-jp to shift-jis.
313                '(iso-2022-jp iso-2022-jp-2 japanese-shift-jis
314                              iso-latin-1 utf-8)))))
315   "Preferred coding systems for encoding outgoing messages.
316
317 More than one suitable coding system may be found for some text.
318 By default, the coding system with the highest priority is used
319 to encode outgoing messages (see `sort-coding-systems').  If this
320 variable is set, it overrides the default priority."
321   :type '(repeat (symbol :tag "Coding system"))
322   :group 'mime)
323
324 ;; ??
325 (defvar mm-use-find-coding-systems-region
326   (fboundp 'find-coding-systems-region)
327   "Use `find-coding-systems-region' to find proper coding systems.
328
329 Setting it to nil is useful on Emacsen supporting Unicode if sending
330 mail with multiple parts is preferred to sending a Unicode one.")
331
332 ;;; Internal variables:
333
334 ;;; Functions:
335
336 (defun mm-mule-charset-to-mime-charset (charset)
337   "Return the MIME charset corresponding to the given Mule CHARSET."
338   (if (and (fboundp 'find-coding-systems-for-charsets)
339            (fboundp 'sort-coding-systems))
340       (let (mime)
341         (dolist (cs (sort-coding-systems
342                      (copy-sequence
343                       (find-coding-systems-for-charsets (list charset)))))
344           (unless mime
345             (when cs
346               (setq mime (or (coding-system-get cs :mime-charset)
347                              (coding-system-get cs 'mime-charset))))))
348         mime)
349     (let ((alist mm-mime-mule-charset-alist)
350           out)
351       (while alist
352         (when (memq charset (cdar alist))
353           (setq out (caar alist)
354                 alist nil))
355         (pop alist))
356       out)))
357
358 (defun mm-charset-to-coding-system (charset &optional lbt)
359   "Return coding-system corresponding to CHARSET.
360 CHARSET is a symbol naming a MIME charset.
361 If optional argument LBT (`unix', `dos' or `mac') is specified, it is
362 used as the line break code type of the coding system."
363   (when (stringp charset)
364     (setq charset (intern (downcase charset))))
365   (when lbt
366     (setq charset (intern (format "%s-%s" charset lbt))))
367   (cond
368    ((null charset)
369     charset)
370    ;; Running in a non-MULE environment.
371    ((or (null (mm-get-coding-system-list))
372         (not (fboundp 'coding-system-get)))
373     charset)
374    ;; ascii
375    ((eq charset 'us-ascii)
376     'ascii)
377    ;; Check to see whether we can handle this charset.  (This depends
378    ;; on there being some coding system matching each `mime-charset'
379    ;; property defined, as there should be.)
380    ((and (mm-coding-system-p charset)
381 ;;; Doing this would potentially weed out incorrect charsets.
382 ;;;      charset
383 ;;;      (eq charset (coding-system-get charset 'mime-charset))
384          )
385     charset)
386    ;; Translate invalid charsets.
387    ((let ((cs (cdr (assq charset mm-charset-synonym-alist))))
388       (and cs (mm-coding-system-p cs) cs)))
389    ;; Last resort: search the coding system list for entries which
390    ;; have the right mime-charset in case the canonical name isn't
391    ;; defined (though it should be).
392    ((let (cs)
393       ;; mm-get-coding-system-list returns a list of cs without lbt.
394       ;; Do we need -lbt?
395       (dolist (c (mm-get-coding-system-list))
396         (if (and (null cs)
397                  (eq charset (or (coding-system-get c :mime-charset)
398                                  (coding-system-get c 'mime-charset))))
399             (setq cs c)))
400       cs))))
401
402 (eval-and-compile
403   (defvar mm-emacs-mule (and (not (featurep 'xemacs))
404                              (boundp 'default-enable-multibyte-characters)
405                              default-enable-multibyte-characters
406                              (fboundp 'set-buffer-multibyte))
407     "True in Emacs with Mule.")
408
409   (if mm-emacs-mule
410       (defun mm-enable-multibyte ()
411         "Set the multibyte flag of the current buffer.
412 Only do this if the default value of `enable-multibyte-characters' is
413 non-nil.  This is a no-op in XEmacs."
414         (set-buffer-multibyte 'to))
415     (defalias 'mm-enable-multibyte 'ignore))
416
417   (if mm-emacs-mule
418       (defun mm-disable-multibyte ()
419         "Unset the multibyte flag of in the current buffer.
420 This is a no-op in XEmacs."
421         (set-buffer-multibyte nil))
422     (defalias 'mm-disable-multibyte 'ignore)))
423
424 (defun mm-preferred-coding-system (charset)
425   ;; A typo in some Emacs versions.
426   (or (get-charset-property charset 'preferred-coding-system)
427       (get-charset-property charset 'prefered-coding-system)))
428
429 ;; Mule charsets shouldn't be used.
430 (defsubst mm-guess-charset ()
431   "Guess Mule charset from the language environment."
432   (or
433    mail-parse-mule-charset ;; cached mule-charset
434    (progn
435      (setq mail-parse-mule-charset
436            (and (boundp 'current-language-environment)
437                 (car (last
438                       (assq 'charset
439                             (assoc current-language-environment
440                                    language-info-alist))))))
441      (if (or (not mail-parse-mule-charset)
442              (eq mail-parse-mule-charset 'ascii))
443          (setq mail-parse-mule-charset
444                (or (car (last (assq mail-parse-charset
445                                     mm-mime-mule-charset-alist)))
446                    ;; default
447                    'latin-iso8859-1)))
448      mail-parse-mule-charset)))
449
450 (defun mm-charset-after (&optional pos)
451   "Return charset of a character in current buffer at position POS.
452 If POS is nil, it defauls to the current point.
453 If POS is out of range, the value is nil.
454 If the charset is `composition', return the actual one."
455   (let ((char (char-after pos)) charset)
456     (if (< (mm-char-int char) 128)
457         (setq charset 'ascii)
458       ;; charset-after is fake in some Emacsen.
459       (setq charset (and (fboundp 'char-charset) (char-charset char)))
460       (if (eq charset 'composition)     ; Mule 4
461           (let ((p (or pos (point))))
462             (cadr (find-charset-region p (1+ p))))
463         (if (and charset (not (memq charset '(ascii eight-bit-control
464                                                     eight-bit-graphic))))
465             charset
466           (mm-guess-charset))))))
467
468 (defun mm-mime-charset (charset)
469   "Return the MIME charset corresponding to the given Mule CHARSET."
470   (if (eq charset 'unknown)
471       (error "The message contains non-printable characters, please use attachment"))
472   (if (and (fboundp 'coding-system-get) (fboundp 'get-charset-property))
473       ;; This exists in Emacs 20.
474       (or
475        (and (mm-preferred-coding-system charset)
476             (or (coding-system-get
477                  (mm-preferred-coding-system charset) :mime-charset)
478                 (coding-system-get
479                  (mm-preferred-coding-system charset) 'mime-charset)))
480        (and (eq charset 'ascii)
481             'us-ascii)
482        (mm-preferred-coding-system charset)
483        (mm-mule-charset-to-mime-charset charset))
484     ;; This is for XEmacs.
485     (mm-mule-charset-to-mime-charset charset)))
486
487 (defun mm-delete-duplicates (list)
488   "Simple substitute for CL `delete-duplicates', testing with `equal'."
489   (let (result head)
490     (while list
491       (setq head (car list))
492       (setq list (delete head list))
493       (setq result (cons head result)))
494     (nreverse result)))
495
496 ;; Fixme:  This is used in places when it should be testing the
497 ;; default multibyteness.  See mm-default-multibyte-p.
498 (eval-and-compile
499   (if (and (not (featurep 'xemacs))
500            (boundp 'enable-multibyte-characters))
501       (defun mm-multibyte-p ()
502         "Non-nil if multibyte is enabled in the current buffer."
503         enable-multibyte-characters)
504     (defun mm-multibyte-p () (featurep 'mule))))
505
506 (defun mm-default-multibyte-p ()
507   "Return non-nil if the session is multibyte.
508 This affects whether coding conversion should be attempted generally."
509   (if (featurep 'mule)
510       (if (boundp 'default-enable-multibyte-characters)
511           default-enable-multibyte-characters
512         t)))
513
514 (defun mm-iso-8859-x-to-15-region (&optional b e)
515   (if (fboundp 'char-charset)
516       (let (charset item c inconvertible)
517         (save-restriction
518           (if e (narrow-to-region b e))
519           (goto-char (point-min))
520           (skip-chars-forward "\0-\177")
521           (while (not (eobp))
522             (cond
523              ((not (setq item (assq (char-charset (setq c (char-after)))
524                                     mm-iso-8859-x-to-15-table)))
525               (forward-char))
526              ((memq c (cdr (cdr item)))
527               (setq inconvertible t)
528               (forward-char))
529              (t
530               (insert-before-markers (prog1 (+ c (car (cdr item)))
531                                        (delete-char 1)))))
532             (skip-chars-forward "\0-\177")))
533         (not inconvertible))))
534
535 (defun mm-sort-coding-systems-predicate (a b)
536   (let ((priorities
537          (mapcar (lambda (cs)
538                    ;; Note: invalid entries are dropped silently
539                    (and (coding-system-p cs)
540                         (coding-system-base cs)))
541                  mm-coding-system-priorities)))
542     (> (length (memq a priorities))
543        (length (memq b priorities)))))
544
545 (defun mm-find-mime-charset-region (b e &optional hack-charsets)
546   "Return the MIME charsets needed to encode the region between B and E.
547 nil means ASCII, a single-element list represents an appropriate MIME
548 charset, and a longer list means no appropriate charset."
549   (let (charsets)
550     ;; The return possibilities of this function are a mess...
551     (or (and (mm-multibyte-p)
552              mm-use-find-coding-systems-region
553              ;; Find the mime-charset of the most preferred coding
554              ;; system that has one.
555              (let ((systems (find-coding-systems-region b e)))
556                (when mm-coding-system-priorities
557                  (setq systems
558                        (sort systems 'mm-sort-coding-systems-predicate)))
559                (setq systems (delq 'compound-text systems))
560                (unless (equal systems '(undecided))
561                  (while systems
562                    (let* ((head (pop systems))
563                           (cs (or (coding-system-get head :mime-charset)
564                                   (coding-system-get head 'mime-charset))))
565                      ;; The mime-charset (`x-ctext') of
566                      ;; `compound-text' is not in the IANA list.  We
567                      ;; shouldn't normally use anything here with a
568                      ;; mime-charset having an `x-' prefix.
569                      ;; Fixme:  Allow this to be overridden, since
570                      ;; there is existing use of x-ctext.
571                      ;; Also people apparently need the coding system
572                      ;; `iso-2022-jp-3' (which Mule-UCS defines with
573                      ;; mime-charset, though it's not valid).
574                      (if (and cs
575                               (not (string-match "^[Xx]-" (symbol-name cs)))
576                               ;; UTF-16 of any variety is invalid for
577                               ;; text parts and, unfortunately, has
578                               ;; mime-charset defined both in Mule-UCS
579                               ;; and versions of Emacs.  (The name
580                               ;; might be `mule-utf-16...'  or
581                               ;; `utf-16...'.)
582                               (not (string-match "utf-16" (symbol-name cs))))
583                          (setq systems nil
584                                charsets (list cs))))))
585                charsets))
586         ;; Otherwise we're not multibyte, we're XEmacs, or a single
587         ;; coding system won't cover it.
588         (setq charsets
589               (mm-delete-duplicates
590                (mapcar 'mm-mime-charset
591                        (delq 'ascii
592                              (mm-find-charset-region b e))))))
593     (if (and (> (length charsets) 1)
594              (memq 'iso-8859-15 charsets)
595              (memq 'iso-8859-15 hack-charsets)
596              (save-excursion (mm-iso-8859-x-to-15-region b e)))
597         (mapcar (lambda (x) (setq charsets (delq (car x) charsets)))
598                 mm-iso-8859-15-compatible))
599     (if (and (memq 'iso-2022-jp-2 charsets)
600              (memq 'iso-2022-jp-2 hack-charsets))
601         (setq charsets (delq 'iso-2022-jp charsets)))
602     charsets))
603
604 (defmacro mm-with-unibyte-buffer (&rest forms)
605   "Create a temporary buffer, and evaluate FORMS there like `progn'.
606 Use unibyte mode for this."
607   `(let (default-enable-multibyte-characters)
608      (with-temp-buffer ,@forms)))
609 (put 'mm-with-unibyte-buffer 'lisp-indent-function 0)
610 (put 'mm-with-unibyte-buffer 'edebug-form-spec '(body))
611
612 (defmacro mm-with-multibyte-buffer (&rest forms)
613   "Create a temporary buffer, and evaluate FORMS there like `progn'.
614 Use multibyte mode for this."
615   `(let ((default-enable-multibyte-characters t))
616      (with-temp-buffer ,@forms)))
617 (put 'mm-with-multibyte-buffer 'lisp-indent-function 0)
618 (put 'mm-with-multibyte-buffer 'edebug-form-spec '(body))
619
620 (defmacro mm-with-unibyte-current-buffer (&rest forms)
621   "Evaluate FORMS with current buffer temporarily made unibyte.
622 Also bind `default-enable-multibyte-characters' to nil.
623 Equivalent to `progn' in XEmacs"
624   (let ((multibyte (make-symbol "multibyte"))
625         (buffer (make-symbol "buffer")))
626     `(if mm-emacs-mule
627          (let ((,multibyte enable-multibyte-characters)
628                (,buffer (current-buffer)))
629            (unwind-protect
630                (let (default-enable-multibyte-characters)
631                  (set-buffer-multibyte nil)
632                  ,@forms)
633              (set-buffer ,buffer)
634              (set-buffer-multibyte ,multibyte)))
635        (let (default-enable-multibyte-characters)
636          ,@forms))))
637 (put 'mm-with-unibyte-current-buffer 'lisp-indent-function 0)
638 (put 'mm-with-unibyte-current-buffer 'edebug-form-spec '(body))
639
640 (defmacro mm-with-unibyte (&rest forms)
641   "Eval the FORMS with the default value of `enable-multibyte-characters' nil."
642   `(let (default-enable-multibyte-characters)
643      ,@forms))
644 (put 'mm-with-unibyte 'lisp-indent-function 0)
645 (put 'mm-with-unibyte 'edebug-form-spec '(body))
646
647 (defmacro mm-with-multibyte (&rest forms)
648   "Eval the FORMS with the default value of `enable-multibyte-characters' t."
649   `(let ((default-enable-multibyte-characters t))
650      ,@forms))
651 (put 'mm-with-multibyte 'lisp-indent-function 0)
652 (put 'mm-with-multibyte 'edebug-form-spec '(body))
653
654 (defun mm-find-charset-region (b e)
655   "Return a list of Emacs charsets in the region B to E."
656   (cond
657    ((and (mm-multibyte-p)
658          (fboundp 'find-charset-region))
659     ;; Remove composition since the base charsets have been included.
660     ;; Remove eight-bit-*, treat them as ascii.
661     (let ((css (find-charset-region b e)))
662       (mapcar (lambda (cs) (setq css (delq cs css)))
663               '(composition eight-bit-control eight-bit-graphic
664                             control-1))
665       css))
666    (t
667     ;; We are in a unibyte buffer or XEmacs non-mule, so we futz around a bit.
668     (save-excursion
669       (save-restriction
670         (narrow-to-region b e)
671         (goto-char (point-min))
672         (skip-chars-forward "\0-\177")
673         (if (eobp)
674             '(ascii)
675           (let (charset)
676             (setq charset
677                   (and (boundp 'current-language-environment)
678                        (car (last (assq 'charset
679                                         (assoc current-language-environment
680                                                language-info-alist))))))
681             (if (eq charset 'ascii) (setq charset nil))
682             (or charset
683                 (setq charset
684                       (car (last (assq mail-parse-charset
685                                        mm-mime-mule-charset-alist)))))
686             (list 'ascii (or charset 'latin-iso8859-1)))))))))
687
688 (defun mm-auto-mode-alist ()
689   "Return an `auto-mode-alist' with only the .gz (etc) thingies."
690   (let ((alist auto-mode-alist)
691         out)
692     (while alist
693       (when (listp (cdar alist))
694         (push (car alist) out))
695       (pop alist))
696     (nreverse out)))
697
698 (defvar mm-inhibit-file-name-handlers
699   '(jka-compr-handler image-file-handler)
700   "A list of handlers doing (un)compression (etc) thingies.")
701
702 (defun mm-insert-file-contents (filename &optional visit beg end replace
703                                          inhibit)
704   "Like `insert-file-contents', but only reads in the file.
705 A buffer may be modified in several ways after reading into the buffer due
706 to advanced Emacs features, such as file-name-handlers, format decoding,
707 `find-file-hooks', etc.
708 If INHIBIT is non-nil, inhibit `mm-inhibit-file-name-handlers'.
709   This function ensures that none of these modifications will take place."
710   (let ((format-alist nil)
711         (auto-mode-alist (if inhibit nil (mm-auto-mode-alist)))
712         (default-major-mode 'fundamental-mode)
713         (enable-local-variables nil)
714         (after-insert-file-functions nil)
715         (enable-local-eval nil)
716         (find-file-hooks nil)
717         (inhibit-file-name-operation (if inhibit
718                                          'insert-file-contents
719                                        inhibit-file-name-operation))
720         (inhibit-file-name-handlers
721          (if inhibit
722              (append mm-inhibit-file-name-handlers
723                      inhibit-file-name-handlers)
724            inhibit-file-name-handlers)))
725     (insert-file-contents filename visit beg end replace)))
726
727 (defun mm-append-to-file (start end filename &optional codesys inhibit)
728   "Append the contents of the region to the end of file FILENAME.
729 When called from a function, expects three arguments,
730 START, END and FILENAME.  START and END are buffer positions
731 saying what text to write.
732 Optional fourth argument specifies the coding system to use when
733 encoding the file.
734 If INHIBIT is non-nil, inhibit `mm-inhibit-file-name-handlers'."
735   (let ((coding-system-for-write
736          (or codesys mm-text-coding-system-for-write
737              mm-text-coding-system))
738         (inhibit-file-name-operation (if inhibit
739                                          'append-to-file
740                                        inhibit-file-name-operation))
741         (inhibit-file-name-handlers
742          (if inhibit
743              (append mm-inhibit-file-name-handlers
744                      inhibit-file-name-handlers)
745            inhibit-file-name-handlers)))
746     (write-region start end filename t 'no-message)
747     (message "Appended to %s" filename)))
748
749 (defun mm-write-region (start end filename &optional append visit lockname
750                               coding-system inhibit)
751
752   "Like `write-region'.
753 If INHIBIT is non-nil, inhibit `mm-inhibit-file-name-handlers'."
754   (let ((coding-system-for-write
755          (or coding-system mm-text-coding-system-for-write
756              mm-text-coding-system))
757         (inhibit-file-name-operation (if inhibit
758                                          'write-region
759                                        inhibit-file-name-operation))
760         (inhibit-file-name-handlers
761          (if inhibit
762              (append mm-inhibit-file-name-handlers
763                      inhibit-file-name-handlers)
764            inhibit-file-name-handlers)))
765     (write-region start end filename append visit lockname)))
766
767 (defun mm-image-load-path (&optional package)
768   (let (dir result)
769     (dolist (path load-path (nreverse result))
770       (when (and path
771                  (file-directory-p
772                   (setq dir (concat (file-name-directory
773                                      (directory-file-name path))
774                                     "etc/" (or package "gnus/")))))
775         (push dir result))
776       (push path result))))
777
778 ;; Fixme: This doesn't look useful where it's used.
779 (if (fboundp 'detect-coding-region)
780     (defun mm-detect-coding-region (start end)
781       "Like `detect-coding-region' except returning the best one."
782       (let ((coding-systems
783              (detect-coding-region (point) (point-max))))
784         (or (car-safe coding-systems)
785             coding-systems)))
786   (defun mm-detect-coding-region (start end)
787     (let ((point (point)))
788       (goto-char start)
789       (skip-chars-forward "\0-\177" end)
790       (prog1
791           (if (eq (point) end) 'ascii (mm-guess-charset))
792         (goto-char point)))))
793
794 (if (fboundp 'coding-system-get)
795     (defun mm-detect-mime-charset-region (start end)
796       "Detect MIME charset of the text in the region between START and END."
797       (let ((cs (mm-detect-coding-region start end)))
798         (coding-system-get cs 'mime-charset)))
799   (defun mm-detect-mime-charset-region (start end)
800     "Detect MIME charset of the text in the region between START and END."
801     (let ((cs (mm-detect-coding-region start end)))
802       cs)))
803
804
805 (provide 'mm-util)
806
807 ;;; arch-tag: 94dc5388-825d-4fd1-bfa5-2100aa351238
808 ;;; mm-util.el ends here