1 ;;; mml.el --- package for parsing and validating MML documents
2 ;; Copyright (C) 1998, 1999, 2000, 2001, 2002 Free Software Foundation, Inc.
4 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
5 ;; This file is part of GNU Emacs.
7 ;; GNU Emacs is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published by
9 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; GNU Emacs is distributed in the hope that it will be useful,
13 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ;; GNU General Public License for more details.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with GNU Emacs; see the file COPYING. If not, write to the
19 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 ;; Boston, MA 02111-1307, USA.
30 (eval-when-compile (require 'cl
))
33 (autoload 'message-make-message-id
"message")
34 (autoload 'gnus-setup-posting-charset
"gnus-msg")
35 (autoload 'gnus-add-minor-mode
"gnus-ems")
36 (autoload 'message-fetch-field
"message")
37 (autoload 'message-posting-charset
"message"))
39 (defvar mml-generate-multipart-alist nil
40 "*Alist of multipart generation functions.
41 Each entry has the form (NAME . FUNCTION), where
42 NAME is a string containing the name of the part (without the
43 leading \"/multipart/\"),
44 FUNCTION is a Lisp function which is called to generate the part.
46 The Lisp function has to supply the appropriate MIME headers and the
47 contents of this part.")
49 (defvar mml-syntax-table
50 (let ((table (copy-syntax-table emacs-lisp-mode-syntax-table
)))
51 (modify-syntax-entry ?
\\ "/" table
)
52 (modify-syntax-entry ?
< "(" table
)
53 (modify-syntax-entry ?
> ")" table
)
54 (modify-syntax-entry ?
@ "w" table
)
55 (modify-syntax-entry ?
/ "w" table
)
56 (modify-syntax-entry ?
= " " table
)
57 (modify-syntax-entry ?
* " " table
)
58 (modify-syntax-entry ?\
; " " table)
59 (modify-syntax-entry ?
\' " " table
)
62 (defvar mml-boundary-function
'mml-make-boundary
63 "A function called to suggest a boundary.
64 The function may be called several times, and should try to make a new
65 suggestion each time. The function is called with one parameter,
66 which is a number that says how many times the function has been
67 called for this message.")
69 (defvar mml-confirmation-set nil
70 "A list of symbols, each of which disables some warning.
71 `unknown-encoding': always send messages contain characters with
72 unknown encoding; `use-ascii': always use ASCII for those characters
73 with unknown encoding; `multipart': always send messages with more than
76 (defvar mml-generate-mime-preprocess-function nil
77 "A function called before generating a mime part.
78 The function is called with one parameter, which is the part to be
81 (defvar mml-generate-mime-postprocess-function nil
82 "A function called after generating a mime part.
83 The function is called with one parameter, which is the generated part.")
85 (defvar mml-generate-default-type
"text/plain")
87 (defvar mml-buffer-list nil
)
89 (defun mml-generate-new-buffer (name)
90 (let ((buf (generate-new-buffer name
)))
91 (push buf mml-buffer-list
)
94 (defun mml-destroy-buffers ()
95 (let (kill-buffer-hook)
96 (mapcar 'kill-buffer mml-buffer-list
)
97 (setq mml-buffer-list nil
)))
100 "Parse the current buffer as an MML document."
101 (goto-char (point-min))
102 (let ((table (syntax-table)))
105 (set-syntax-table mml-syntax-table
)
107 (set-syntax-table table
))))
109 (defun mml-parse-1 ()
110 "Parse the current buffer as an MML document."
111 (let (struct tag point contents charsets warn use-ascii no-markup-p raw
)
112 (while (and (not (eobp))
113 (not (looking-at "<#/multipart")))
115 ((looking-at "<#multipart")
116 (push (nconc (mml-read-tag) (mml-parse-1)) struct
))
117 ((looking-at "<#external")
118 (push (nconc (mml-read-tag) (list (cons 'contents
(mml-read-part))))
121 (if (or (looking-at "<#part") (looking-at "<#mml"))
122 (setq tag
(mml-read-tag)
125 (setq tag
(list 'part
'(type .
"text/plain"))
128 (setq raw
(cdr (assq 'raw tag
))
130 contents
(mml-read-part (eq 'mml
(car tag
)))
132 (mm-find-mime-charset-region point
(point))))
133 (when (and (not raw
) (memq nil charsets
))
134 (if (or (memq 'unknown-encoding mml-confirmation-set
)
137 Message contains characters with unknown encoding. Really send?")
138 (set (make-local-variable 'mml-confirmation-set
)
139 (push 'unknown-encoding mml-confirmation-set
))))
141 (or (memq 'use-ascii mml-confirmation-set
)
142 (y-or-n-p "Use ASCII as charset?")))
143 (setq charsets
(delq nil charsets
))
145 (error "Edit your message to remove those characters")))
148 (< (length charsets
) 2))
149 (if (or (not no-markup-p
)
150 (string-match "[^ \t\r\n]" contents
))
151 ;; Don't create blank parts.
152 (push (nconc tag
(list (cons 'contents contents
)))
154 (let ((nstruct (mml-parse-singlepart-with-multiple-charsets
155 tag point
(point) use-ascii
)))
157 (not (memq 'multipart mml-confirmation-set
))
162 A message part needs to be split into %d charset parts. Really send? "
164 (set (make-local-variable 'mml-confirmation-set
)
165 (push 'multipart mml-confirmation-set
)))))
166 (error "Edit your message to use only one charset"))
167 (setq struct
(nconc nstruct struct
)))))))
172 (defun mml-parse-singlepart-with-multiple-charsets
173 (orig-tag beg end
&optional use-ascii
)
176 (narrow-to-region beg end
)
177 (goto-char (point-min))
178 (let ((current (or (mm-mime-charset (mm-charset-after))
179 (and use-ascii
'us-ascii
)))
180 charset struct space newline paragraph
)
182 (setq charset
(mm-mime-charset (mm-charset-after)))
184 ;; The charset remains the same.
185 ((eq charset
'us-ascii
))
186 ((or (and use-ascii
(not charset
))
187 (eq charset current
))
191 ;; The initial charset was ascii.
192 ((eq current
'us-ascii
)
193 (setq current charset
197 ;; We have a change in charsets.
201 (list (cons 'contents
202 (buffer-substring-no-properties
203 beg
(or paragraph newline space
(point))))))
205 (setq beg
(or paragraph newline space
(point))
210 ;; Compute places where it might be nice to break the part.
212 ((memq (following-char) '(? ?
\t))
213 (setq space
(1+ (point))))
214 ((and (eq (following-char) ?
\n)
216 (eq (char-after (1- (point))) ?
\n))
217 (setq paragraph
(point)))
218 ((eq (following-char) ?
\n)
219 (setq newline
(1+ (point)))))
221 ;; Do the final part.
222 (unless (= beg
(point))
223 (push (append orig-tag
224 (list (cons 'contents
225 (buffer-substring-no-properties
230 (defun mml-read-tag ()
231 "Read a tag and return the contents."
232 (let (contents name elem val
)
234 (setq name
(buffer-substring-no-properties
235 (point) (progn (forward-sexp 1) (point))))
236 (skip-chars-forward " \t\n")
237 (while (not (looking-at ">"))
238 (setq elem
(buffer-substring-no-properties
239 (point) (progn (forward-sexp 1) (point))))
240 (skip-chars-forward "= \t\n")
241 (setq val
(buffer-substring-no-properties
242 (point) (progn (forward-sexp 1) (point))))
243 (when (string-match "^\"\\(.*\\)\"$" val
)
244 (setq val
(match-string 1 val
)))
245 (push (cons (intern elem
) val
) contents
)
246 (skip-chars-forward " \t\n"))
248 (skip-chars-forward " \t\n")
249 (cons (intern name
) (nreverse contents
))))
251 (defun mml-read-part (&optional mml
)
252 "Return the buffer up till the next part, multipart or closing part or multipart.
253 If MML is non-nil, return the buffer up till the correspondent mml tag."
254 (let ((beg (point)) (count 1))
255 ;; If the tag ended at the end of the line, we go to the next line.
256 (when (looking-at "[ \t]*\n")
260 (while (and (> count
0) (not (eobp)))
261 (if (re-search-forward "<#\\(/\\)?mml." nil t
)
262 (setq count
(+ count
(if (match-beginning 1) -
1 1)))
263 (goto-char (point-max))))
264 (buffer-substring-no-properties beg
(if (> count
0)
266 (match-beginning 0))))
267 (if (re-search-forward
268 "<#\\(/\\)?\\(multipart\\|part\\|external\\|mml\\)." nil t
)
270 (buffer-substring-no-properties beg
(match-beginning 0))
271 (if (or (not (match-beginning 1))
272 (equal (match-string 2) "multipart"))
273 (goto-char (match-beginning 0))
274 (when (looking-at "[ \t]*\n")
276 (buffer-substring-no-properties beg
(goto-char (point-max)))))))
278 (defvar mml-boundary nil
)
279 (defvar mml-base-boundary
"-=-=")
280 (defvar mml-multipart-number
0)
282 (defun mml-generate-mime ()
283 "Generate a MIME message based on the current MML document."
284 (let ((cont (mml-parse))
285 (mml-multipart-number mml-multipart-number
))
289 (if (and (consp (car cont
))
291 (mml-generate-mime-1 (car cont
))
292 (mml-generate-mime-1 (nconc (list 'multipart
'(type .
"mixed"))
296 (defun mml-generate-mime-1 (cont)
298 (narrow-to-region (point) (point))
299 (if mml-generate-mime-preprocess-function
300 (funcall mml-generate-mime-preprocess-function cont
))
302 ((or (eq (car cont
) 'part
) (eq (car cont
) 'mml
))
303 (let ((raw (cdr (assq 'raw cont
)))
304 coded encoding charset filename type
)
305 (setq type
(or (cdr (assq 'type cont
)) "text/plain"))
307 (member (car (split-string type
"/")) '("text" "message")))
311 ((cdr (assq 'buffer cont
))
312 (insert-buffer-substring (cdr (assq 'buffer cont
))))
313 ((and (setq filename
(cdr (assq 'filename cont
)))
314 (not (equal (cdr (assq 'nofile cont
)) "yes")))
315 (mm-insert-file-contents filename
))
316 ((eq 'mml
(car cont
))
317 (insert (cdr (assq 'contents cont
))))
320 (narrow-to-region (point) (point))
321 (insert (cdr (assq 'contents cont
)))
322 ;; Remove quotes from quoted tags.
323 (goto-char (point-min))
324 (while (re-search-forward
325 "<#!+/?\\(part\\|multipart\\|external\\|mml\\)" nil t
)
326 (delete-region (+ (match-beginning 0) 2)
327 (+ (match-beginning 0) 3))))))
329 ((eq (car cont
) 'mml
)
330 (let ((mml-boundary (funcall mml-boundary-function
331 (incf mml-multipart-number
)))
332 (mml-generate-default-type "text/plain"))
334 (let ((mm-7bit-chars (concat mm-7bit-chars
"\x1b")))
335 ;; ignore 0x1b, it is part of iso-2022-jp
336 (setq encoding
(mm-body-7-or-8))))
337 ((string= (car (split-string type
"/")) "message")
338 (let ((mm-7bit-chars (concat mm-7bit-chars
"\x1b")))
339 ;; ignore 0x1b, it is part of iso-2022-jp
340 (setq encoding
(mm-body-7-or-8))))
342 (setq charset
(mm-encode-body))
343 (setq encoding
(mm-body-encoding
344 charset
(cdr (assq 'encoding cont
))))))
345 (setq coded
(buffer-string)))
346 (mml-insert-mime-headers cont type charset encoding
)
349 (mm-with-unibyte-buffer
351 ((cdr (assq 'buffer cont
))
352 (insert-buffer-substring (cdr (assq 'buffer cont
))))
353 ((and (setq filename
(cdr (assq 'filename cont
)))
354 (not (equal (cdr (assq 'nofile cont
)) "yes")))
355 (let ((coding-system-for-read mm-binary-coding-system
))
356 (mm-insert-file-contents filename nil nil nil nil t
)))
358 (insert (cdr (assq 'contents cont
)))))
359 (setq encoding
(mm-encode-buffer type
)
360 coded
(buffer-string)))
361 (mml-insert-mime-headers cont type charset encoding
)
363 (mm-with-unibyte-current-buffer
365 ((eq (car cont
) 'external
)
366 (insert "Content-Type: message/external-body")
367 (let ((parameters (mml-parameter-string
368 cont
'(expiration size permission
)))
369 (name (cdr (assq 'name cont
))))
371 (setq name
(mml-parse-file-name name
))
373 (mml-insert-parameter
374 (mail-header-encode-parameter "name" name
)
375 "access-type=local-file")
376 (mml-insert-parameter
377 (mail-header-encode-parameter
378 "name" (file-name-nondirectory (nth 2 name
)))
379 (mail-header-encode-parameter "site" (nth 1 name
))
380 (mail-header-encode-parameter
381 "directory" (file-name-directory (nth 2 name
))))
382 (mml-insert-parameter
383 (concat "access-type="
384 (if (member (nth 0 name
) '("ftp@" "anonymous@"))
388 (mml-insert-parameter-string
389 cont
'(expiration size permission
))))
391 (insert "Content-Type: " (cdr (assq 'type cont
)) "\n")
392 (insert "Content-ID: " (message-make-message-id) "\n")
393 (insert "Content-Transfer-Encoding: "
394 (or (cdr (assq 'encoding cont
)) "binary"))
396 (insert (or (cdr (assq 'contents cont
))))
398 ((eq (car cont
) 'multipart
)
399 (let* ((type (or (cdr (assq 'type cont
)) "mixed"))
400 (mml-generate-default-type (if (equal type
"digest")
403 (handler (assoc type mml-generate-multipart-alist
)))
405 (funcall (cdr handler
) cont
)
406 ;; No specific handler. Use default one.
407 (let ((mml-boundary (mml-compute-boundary cont
)))
408 (insert (format "Content-Type: multipart/%s; boundary=\"%s\"\n"
410 ;; Skip `multipart' and `type' elements.
411 (setq cont
(cddr cont
))
413 (insert "\n--" mml-boundary
"\n")
414 (mml-generate-mime-1 (pop cont
)))
415 (insert "\n--" mml-boundary
"--\n")))))
417 (error "Invalid element: %S" cont
)))
418 (if mml-generate-mime-postprocess-function
419 (funcall mml-generate-mime-postprocess-function cont
))))
421 (defun mml-compute-boundary (cont)
422 "Return a unique boundary that does not exist in CONT."
423 (let ((mml-boundary (funcall mml-boundary-function
424 (incf mml-multipart-number
))))
425 ;; This function tries again and again until it has found
426 ;; a unique boundary.
427 (while (not (catch 'not-unique
428 (mml-compute-boundary-1 cont
))))
431 (defun mml-compute-boundary-1 (cont)
434 ((eq (car cont
) 'part
)
437 ((cdr (assq 'buffer cont
))
438 (insert-buffer-substring (cdr (assq 'buffer cont
))))
439 ((and (setq filename
(cdr (assq 'filename cont
)))
440 (not (equal (cdr (assq 'nofile cont
)) "yes")))
441 (mm-insert-file-contents filename
))
443 (insert (cdr (assq 'contents cont
)))))
444 (goto-char (point-min))
445 (when (re-search-forward (concat "^--" (regexp-quote mml-boundary
))
447 (setq mml-boundary
(funcall mml-boundary-function
448 (incf mml-multipart-number
)))
449 (throw 'not-unique nil
))))
450 ((eq (car cont
) 'multipart
)
451 (mapcar 'mml-compute-boundary-1
(cddr cont
))))
454 (defun mml-make-boundary (number)
455 (concat (make-string (% number
60) ?
=)
461 (defun mml-insert-mime-headers (cont type charset encoding
)
462 (let (parameters disposition description
)
464 (mml-parameter-string
465 cont
'(name access-type expiration size permission
)))
468 (not (equal type mml-generate-default-type
)))
469 (when (consp charset
)
471 "Can't encode a part with several charsets."))
472 (insert "Content-Type: " type
)
474 (insert "; " (mail-header-encode-parameter
475 "charset" (symbol-name charset
))))
477 (mml-insert-parameter-string
478 cont
'(name access-type expiration size permission
)))
481 (mml-parameter-string
482 cont
'(filename creation-date modification-date read-date
)))
483 (when (or (setq disposition
(cdr (assq 'disposition cont
)))
485 (insert "Content-Disposition: " (or disposition
"inline"))
487 (mml-insert-parameter-string
488 cont
'(filename creation-date modification-date read-date
)))
490 (unless (eq encoding
'7bit
)
491 (insert (format "Content-Transfer-Encoding: %s\n" encoding
)))
492 (when (setq description
(cdr (assq 'description cont
)))
493 (insert "Content-Description: "
494 (mail-encode-encoded-word-string description
) "\n"))))
496 (defun mml-parameter-string (cont types
)
499 (while (setq type
(pop types
))
500 (when (setq value
(cdr (assq type cont
)))
501 ;; Strip directory component from the filename parameter.
502 (when (eq type
'filename
)
503 (setq value
(file-name-nondirectory value
)))
504 (setq string
(concat string
"; "
505 (mail-header-encode-parameter
506 (symbol-name type
) value
)))))
507 (when (not (zerop (length string
)))
510 (defun mml-insert-parameter-string (cont types
)
512 (while (setq type
(pop types
))
513 (when (setq value
(cdr (assq type cont
)))
514 ;; Strip directory component from the filename parameter.
515 (when (eq type
'filename
)
516 (setq value
(file-name-nondirectory value
)))
517 (mml-insert-parameter
518 (mail-header-encode-parameter
519 (symbol-name type
) value
))))))
522 (defvar ange-ftp-name-format
)
523 (defvar efs-path-regexp
))
524 (defun mml-parse-file-name (path)
525 (if (if (boundp 'efs-path-regexp
)
526 (string-match efs-path-regexp path
)
527 (if (boundp 'ange-ftp-name-format
)
528 (string-match (car ange-ftp-name-format
) path
)))
529 (list (match-string 1 path
) (match-string 2 path
)
530 (substring path
(1+ (match-end 2))))
533 (defun mml-insert-buffer (buffer)
534 "Insert BUFFER at point and quote any MML markup."
536 (narrow-to-region (point) (point))
537 (insert-buffer-substring buffer
)
538 (mml-quote-region (point-min) (point-max))
539 (goto-char (point-max))))
542 ;;; Transforming MIME to MML
545 (defun mime-to-mml ()
546 "Translate the current buffer (which should be a message) into MML."
547 ;; First decode the head.
549 (message-narrow-to-head)
550 (mail-decode-encoded-word-region (point-min) (point-max)))
551 (let ((handles (mm-dissect-buffer t
)))
552 (goto-char (point-min))
553 (search-forward "\n\n" nil t
)
554 (delete-region (point) (point-max))
555 (if (stringp (car handles
))
556 (mml-insert-mime handles
)
557 (mml-insert-mime handles t
))
558 (mm-destroy-parts handles
))
560 (message-narrow-to-head)
561 ;; Remove them, they are confusing.
562 (message-remove-header "Content-Type")
563 (message-remove-header "MIME-Version")
564 (message-remove-header "Content-Transfer-Encoding")))
566 (defun mml-to-mime ()
567 "Translate the current buffer from MML to MIME."
568 (message-encode-message-body)
570 (message-narrow-to-headers-or-head)
571 (let ((mail-parse-charset message-default-charset
))
572 (mail-encode-encoded-word-buffer))))
574 (defun mml-insert-mime (handle &optional no-markup
)
575 (let (textp buffer mmlp
)
576 ;; Determine type and stuff.
577 (unless (stringp (car handle
))
578 (unless (setq textp
(equal (mm-handle-media-supertype handle
) "text"))
580 (set-buffer (setq buffer
(mml-generate-new-buffer " *mml*")))
581 (mm-insert-part handle
)
582 (if (setq mmlp
(equal (mm-handle-media-type handle
)
586 (mml-insert-mml-markup handle nil t t
)
587 (unless (and no-markup
588 (equal (mm-handle-media-type handle
) "text/plain"))
589 (mml-insert-mml-markup handle buffer textp
)))
592 (insert-buffer buffer
)
593 (goto-char (point-max))
594 (insert "<#/mml>\n"))
595 ((stringp (car handle
))
596 (mapcar 'mml-insert-mime
(cdr handle
))
597 (insert "<#/multipart>\n"))
599 (let ((text (mm-get-part handle
))
600 (charset (mail-content-type-get
601 (mm-handle-type handle
) 'charset
)))
602 (insert (mm-decode-string text charset
)))
603 (goto-char (point-max)))
605 (insert "<#/part>\n")))))
607 (defun mml-insert-mml-markup (handle &optional buffer nofile mmlp
)
608 "Take a MIME handle and insert an MML tag."
609 (if (stringp (car handle
))
610 (insert "<#multipart type=" (mm-handle-media-subtype handle
)
613 (insert "<#mml type=" (mm-handle-media-type handle
))
614 (insert "<#part type=" (mm-handle-media-type handle
)))
615 (dolist (elem (append (cdr (mm-handle-type handle
))
616 (cdr (mm-handle-disposition handle
))))
617 (insert " " (symbol-name (car elem
)) "=\"" (cdr elem
) "\""))
618 (when (mm-handle-disposition handle
)
619 (insert " disposition=" (car (mm-handle-disposition handle
))))
621 (insert " buffer=\"" (buffer-name buffer
) "\""))
623 (insert " nofile=yes"))
624 (when (mm-handle-description handle
)
625 (insert " description=\"" (mm-handle-description handle
) "\""))
628 (defun mml-insert-parameter (&rest parameters
)
629 "Insert PARAMETERS in a nice way."
630 (dolist (param parameters
)
632 (let ((point (point)))
634 (when (> (current-column) 71)
640 ;;; Mode for inserting and editing MML forms
644 (let ((map (make-sparse-keymap))
645 (main (make-sparse-keymap)))
646 (define-key map
"f" 'mml-attach-file
)
647 (define-key map
"b" 'mml-attach-buffer
)
648 (define-key map
"e" 'mml-attach-external
)
649 (define-key map
"q" 'mml-quote-region
)
650 (define-key map
"m" 'mml-insert-multipart
)
651 (define-key map
"p" 'mml-insert-part
)
652 (define-key map
"v" 'mml-validate
)
653 (define-key map
"P" 'mml-preview
)
654 ;;(define-key map "n" 'mml-narrow-to-part)
655 (define-key main
"\M-m" map
)
659 mml-menu mml-mode-map
""
662 ["File" mml-attach-file t
]
663 ["Buffer" mml-attach-buffer t
]
664 ["External" mml-attach-external t
])
666 ["Multipart" mml-insert-multipart t
]
667 ["Part" mml-insert-part t
])
668 ;;["Narrow" mml-narrow-to-part t]
669 ["Quote" mml-quote-region t
]
670 ["Validate" mml-validate t
]
671 ["Preview" mml-preview t
]))
674 "Minor mode for editing MML.")
676 (defun mml-mode (&optional arg
)
677 "Minor mode for editing MML.
681 (if (not (set (make-local-variable 'mml-mode
)
682 (if (null arg
) (not mml-mode
)
683 (> (prefix-numeric-value arg
) 0))))
685 (set (make-local-variable 'mml-mode
) t
)
686 (unless (assq 'mml-mode minor-mode-alist
)
687 (push `(mml-mode " MML") minor-mode-alist
))
688 (unless (assq 'mml-mode minor-mode-map-alist
)
689 (push (cons 'mml-mode mml-mode-map
)
690 minor-mode-map-alist
)))
691 (run-hooks 'mml-mode-hook
))
694 ;;; Helper functions for reading MIME stuff from the minibuffer and
695 ;;; inserting stuff to the buffer.
698 (defun mml-minibuffer-read-file (prompt)
699 (let ((file (read-file-name prompt nil nil t
)))
700 ;; Prevent some common errors. This is inspired by similar code in
702 (when (file-directory-p file
)
703 (error "%s is a directory, cannot attach" file
))
704 (unless (file-exists-p file
)
705 (error "No such file: %s" file
))
706 (unless (file-readable-p file
)
707 (error "Permission denied: %s" file
))
710 (defun mml-minibuffer-read-type (name &optional default
)
711 (mailcap-parse-mimetypes)
712 (let* ((default (or default
713 (mm-default-file-encoding name
)
714 ;; Perhaps here we should check what the file
715 ;; looks like, and offer text/plain if it looks
717 "application/octet-stream"))
718 (string (completing-read
719 (format "Content type (default %s): " default
)
720 (mapcar 'list
(mailcap-mime-types)))))
721 (if (not (equal string
""))
725 (defun mml-minibuffer-read-description ()
726 (let ((description (read-string "One line description: ")))
727 (when (string-match "\\`[ \t]*\\'" description
)
728 (setq description nil
))
731 (defun mml-quote-region (beg end
)
732 "Quote the MML tags in the region."
736 ;; Temporarily narrow the region to defend from changes
738 (narrow-to-region beg end
)
739 (goto-char (point-min))
741 (while (re-search-forward
742 "<#!*/?\\(multipart\\|part\\|external\\|mml\\)" nil t
)
743 ;; Insert ! after the #.
744 (goto-char (+ (match-beginning 0) 2))
747 (defun mml-insert-tag (name &rest plist
)
748 "Insert an MML tag described by NAME and PLIST."
750 (setq name
(symbol-name name
)))
753 (let ((key (pop plist
))
756 ;; Quote VALUE if it contains suspicious characters.
757 (when (string-match "[\"'\\~/*;() \t\n]" value
)
758 (setq value
(prin1-to-string value
)))
759 (insert (format " %s=%s" key value
)))))
762 (defun mml-insert-empty-tag (name &rest plist
)
763 "Insert an empty MML tag described by NAME and PLIST."
765 (setq name
(symbol-name name
)))
766 (apply #'mml-insert-tag name plist
)
767 (insert "<#/" name
">\n"))
769 ;;; Attachment functions.
771 (defun mml-attach-file (file &optional type description
)
772 "Attach a file to the outgoing MIME message.
773 The file is not inserted or encoded until you send the message with
774 `\\[message-send-and-exit]' or `\\[message-send]'.
776 FILE is the name of the file to attach. TYPE is its content-type, a
777 string of the form \"type/subtype\". DESCRIPTION is a one-line
778 description of the attachment."
780 (let* ((file (mml-minibuffer-read-file "Attach file: "))
781 (type (mml-minibuffer-read-type file
))
782 (description (mml-minibuffer-read-description)))
783 (list file type description
)))
784 (mml-insert-empty-tag 'part
'type type
'filename file
785 'disposition
"attachment" 'description description
))
787 (defun mml-attach-buffer (buffer &optional type description
)
788 "Attach a buffer to the outgoing MIME message.
789 See `mml-attach-file' for details of operation."
791 (let* ((buffer (read-buffer "Attach buffer: "))
792 (type (mml-minibuffer-read-type buffer
"text/plain"))
793 (description (mml-minibuffer-read-description)))
794 (list buffer type description
)))
795 (mml-insert-empty-tag 'part
'type type
'buffer buffer
796 'disposition
"attachment" 'description description
))
798 (defun mml-attach-external (file &optional type description
)
799 "Attach an external file into the buffer.
800 FILE is an ange-ftp/efs specification of the part location.
801 TYPE is the MIME type to use."
803 (let* ((file (mml-minibuffer-read-file "Attach external file: "))
804 (type (mml-minibuffer-read-type file
))
805 (description (mml-minibuffer-read-description)))
806 (list file type description
)))
807 (mml-insert-empty-tag 'external
'type type
'name file
808 'disposition
"attachment" 'description description
))
810 (defun mml-insert-multipart (&optional type
)
811 (interactive (list (completing-read "Multipart type (default mixed): "
812 '(("mixed") ("alternative") ("digest") ("parallel")
813 ("signed") ("encrypted"))
817 (mml-insert-empty-tag "multipart" 'type type
)
820 (defun mml-insert-part (&optional type
)
822 (list (mml-minibuffer-read-type "")))
823 (mml-insert-tag 'part
'type type
'disposition
"inline")
826 (defun mml-preview (&optional raw
)
827 "Display current buffer with Gnus, in a new buffer.
828 If RAW, don't highlight the article."
830 (let ((buf (current-buffer))
831 (message-posting-charset (or (gnus-setup-posting-charset
833 (message-narrow-to-headers-or-head)
834 (message-fetch-field "Newsgroups")))
835 message-posting-charset
)))
836 (switch-to-buffer (get-buffer-create
837 (concat (if raw
"*Raw MIME preview of "
838 "*MIME preview of ") (buffer-name))))
841 (if (re-search-forward
842 (concat "^" (regexp-quote mail-header-separator
) "\n") nil t
)
843 (replace-match "\n"))
844 (let ((mail-header-separator "")) ;; mail-header-separator is removed.
847 (when (fboundp 'set-buffer-multibyte
)
848 (let ((s (buffer-string)))
849 ;; Insert the content into unibyte buffer.
851 (mm-disable-multibyte)
853 (let ((gnus-newsgroup-charset (car message-posting-charset
)))
854 (run-hooks 'gnus-article-decode-hook
)
855 (let ((gnus-newsgroup-name "dummy"))
856 (gnus-article-prepare-display))))
857 ;; Disable article-mode-map.
859 (setq buffer-read-only t
)
860 (local-set-key "q" (lambda () (interactive) (kill-buffer nil
)))
861 (goto-char (point-min))))
863 (defun mml-validate ()
864 "Validate the current MML document."
870 ;;; arch-tag: 583c96cf-1ffe-451b-a5e5-4733ae9ddd12