(read_minibuf): Clean up the binding stack if
[bpt/emacs.git] / lisp / gnus / nndoc.el
CommitLineData
eec82323 1;;; nndoc.el --- single file access for Gnus
16409b0b
GM
2;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000
3;; Free Software Foundation, Inc.
eec82323 4
6748645f 5;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
eec82323
LMI
6;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
7;; Keywords: news
8
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software; you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation; either version 2, or (at your option)
14;; any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs; see the file COPYING. If not, write to the
23;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24;; Boston, MA 02111-1307, USA.
25
26;;; Commentary:
27
28;;; Code:
29
30(require 'nnheader)
31(require 'message)
32(require 'nnmail)
33(require 'nnoo)
6748645f 34(require 'gnus-util)
16409b0b 35(require 'mm-util)
eec82323
LMI
36(eval-when-compile (require 'cl))
37
38(nnoo-declare nndoc)
39
40(defvoo nndoc-article-type 'guess
41 "*Type of the file.
42One of `mbox', `babyl', `digest', `news', `rnews', `mmdf', `forward',
16409b0b
GM
43`rfc934', `rfc822-forward', `mime-parts', `standard-digest',
44`slack-digest', `clari-briefs', `nsmail' or `guess'.")
eec82323
LMI
45
46(defvoo nndoc-post-type 'mail
47 "*Whether the nndoc group is `mail' or `post'.")
48
6748645f
LMI
49(defvoo nndoc-open-document-hook 'nnheader-ms-strip-cr
50 "Hook run after opening a document.
51The default function removes all trailing carriage returns
16409b0b 52from the document.")
6748645f 53
eec82323
LMI
54(defvar nndoc-type-alist
55 `((mmdf
56 (article-begin . "^\^A\^A\^A\^A\n")
57 (body-end . "^\^A\^A\^A\^A\n"))
16409b0b
GM
58 (nsmail
59 (article-begin . "^From - "))
eec82323
LMI
60 (news
61 (article-begin . "^Path:"))
62 (rnews
63 (article-begin . "^#! *rnews +\\([0-9]+\\) *\n")
64 (body-end-function . nndoc-rnews-body-end))
65 (mbox
66 (article-begin-function . nndoc-mbox-article-begin)
67 (body-end-function . nndoc-mbox-body-end))
68 (babyl
69 (article-begin . "\^_\^L *\n")
70 (body-end . "\^_")
71 (body-begin-function . nndoc-babyl-body-begin)
72 (head-begin-function . nndoc-babyl-head-begin))
73 (forward
16409b0b
GM
74 (article-begin . "^-+ \\(Start of \\)?forwarded message.*\n+")
75 (body-end . "^-+ End \\(of \\)?forwarded message.*$")
eec82323
LMI
76 (prepare-body-function . nndoc-unquote-dashes))
77 (rfc934
78 (article-begin . "^--.*\n+")
79 (body-end . "^--.*$")
80 (prepare-body-function . nndoc-unquote-dashes))
81 (clari-briefs
82 (article-begin . "^ \\*")
83 (body-end . "^\t------*[ \t]^*\n^ \\*")
84 (body-begin . "^\t")
85 (head-end . "^\t")
86 (generate-head-function . nndoc-generate-clari-briefs-head)
87 (article-transform-function . nndoc-transform-clari-briefs))
88 (mime-digest
89 (article-begin . "")
16409b0b 90 (head-begin . "^ ?\n")
eec82323
LMI
91 (head-end . "^ ?$")
92 (body-end . "")
93 (file-end . "")
94 (subtype digest guess))
6748645f
LMI
95 (mime-parts
96 (generate-head-function . nndoc-generate-mime-parts-head)
97 (article-transform-function . nndoc-transform-mime-parts))
eec82323 98 (standard-digest
6748645f
LMI
99 (first-article . ,(concat "^" (make-string 70 ?-) "\n *\n+"))
100 (article-begin . ,(concat "^\n" (make-string 30 ?-) "\n *\n+"))
eec82323
LMI
101 (prepare-body-function . nndoc-unquote-dashes)
102 (body-end-function . nndoc-digest-body-end)
6748645f
LMI
103 (head-end . "^ *$")
104 (body-begin . "^ *\n")
eec82323
LMI
105 (file-end . "^End of .*digest.*[0-9].*\n\\*\\*\\|^End of.*Digest *$")
106 (subtype digest guess))
107 (slack-digest
108 (article-begin . "^------------------------------*[\n \t]+")
109 (head-end . "^ ?$")
110 (body-end-function . nndoc-digest-body-end)
111 (body-begin . "^ ?$")
112 (file-end . "^End of")
113 (prepare-body-function . nndoc-unquote-dashes)
114 (subtype digest guess))
115 (lanl-gov-announce
116 (article-begin . "^\\\\\\\\\n")
117 (head-begin . "^Paper.*:")
118 (head-end . "\\(^\\\\\\\\.*\n\\|-----------------\\)")
119 (body-begin . "")
120 (body-end . "-------------------------------------------------")
121 (file-end . "^Title: Recent Seminal")
122 (generate-head-function . nndoc-generate-lanl-gov-head)
123 (article-transform-function . nndoc-transform-lanl-gov-announce)
124 (subtype preprints guess))
125 (rfc822-forward
126 (article-begin . "^\n")
127 (body-end-function . nndoc-rfc822-forward-body-end-function))
16409b0b
GM
128 (outlook
129 (article-begin-function . nndoc-outlook-article-begin)
130 (body-end . "\0"))
eec82323
LMI
131 (guess
132 (guess . t)
133 (subtype nil))
134 (digest
135 (guess . t)
136 (subtype nil))
137 (preprints
138 (guess . t)
139 (subtype nil))))
140
141\f
eec82323
LMI
142(defvoo nndoc-file-begin nil)
143(defvoo nndoc-first-article nil)
eec82323
LMI
144(defvoo nndoc-article-begin nil)
145(defvoo nndoc-head-begin nil)
146(defvoo nndoc-head-end nil)
147(defvoo nndoc-file-end nil)
148(defvoo nndoc-body-begin nil)
149(defvoo nndoc-body-end-function nil)
150(defvoo nndoc-body-begin-function nil)
151(defvoo nndoc-head-begin-function nil)
152(defvoo nndoc-body-end nil)
6748645f 153;; nndoc-dissection-alist is a list of sublists. Each sublist holds the
16409b0b
GM
154;; following items. ARTICLE acts as the association key and is an ordinal
155;; starting at 1. HEAD-BEGIN [0], HEAD-END [1], BODY-BEGIN [2] and BODY-END
156;; [3] are positions in the `nndoc' buffer. LINE-COUNT [4] is a count of
157;; lines in the body. For MIME dissections only, ARTICLE-INSERT [5] and
158;; SUMMARY-INSERT [6] give headers to insert for full article or summary line
159;; generation, respectively. Other headers usually follow directly from the
160;; buffer. Value `nil' means no insert.
eec82323
LMI
161(defvoo nndoc-dissection-alist nil)
162(defvoo nndoc-prepare-body-function nil)
163(defvoo nndoc-generate-head-function nil)
164(defvoo nndoc-article-transform-function nil)
165(defvoo nndoc-article-begin-function nil)
166
167(defvoo nndoc-status-string "")
168(defvoo nndoc-group-alist nil)
169(defvoo nndoc-current-buffer nil
170 "Current nndoc news buffer.")
171(defvoo nndoc-address nil)
172
173(defconst nndoc-version "nndoc 1.0"
174 "nndoc version.")
175
176\f
177
178;;; Interface functions
179
180(nnoo-define-basics nndoc)
181
182(deffoo nndoc-retrieve-headers (articles &optional newsgroup server fetch-old)
183 (when (nndoc-possibly-change-buffer newsgroup server)
184 (save-excursion
185 (set-buffer nntp-server-buffer)
186 (erase-buffer)
187 (let (article entry)
188 (if (stringp (car articles))
189 'headers
190 (while articles
191 (when (setq entry (cdr (assq (setq article (pop articles))
192 nndoc-dissection-alist)))
193 (insert (format "221 %d Article retrieved.\n" article))
194 (if nndoc-generate-head-function
195 (funcall nndoc-generate-head-function article)
196 (insert-buffer-substring
197 nndoc-current-buffer (car entry) (nth 1 entry)))
198 (goto-char (point-max))
16409b0b 199 (unless (eq (char-after (1- (point))) ?\n)
eec82323
LMI
200 (insert "\n"))
201 (insert (format "Lines: %d\n" (nth 4 entry)))
202 (insert ".\n")))
203
204 (nnheader-fold-continuation-lines)
205 'headers)))))
206
207(deffoo nndoc-request-article (article &optional newsgroup server buffer)
208 (nndoc-possibly-change-buffer newsgroup server)
209 (save-excursion
210 (let ((buffer (or buffer nntp-server-buffer))
211 (entry (cdr (assq article nndoc-dissection-alist)))
212 beg)
213 (set-buffer buffer)
214 (erase-buffer)
215 (when entry
216 (if (stringp article)
217 nil
218 (insert-buffer-substring
219 nndoc-current-buffer (car entry) (nth 1 entry))
220 (insert "\n")
221 (setq beg (point))
222 (insert-buffer-substring
223 nndoc-current-buffer (nth 2 entry) (nth 3 entry))
224 (goto-char beg)
225 (when nndoc-prepare-body-function
226 (funcall nndoc-prepare-body-function))
227 (when nndoc-article-transform-function
228 (funcall nndoc-article-transform-function article))
229 t)))))
230
231(deffoo nndoc-request-group (group &optional server dont-check)
232 "Select news GROUP."
233 (let (number)
234 (cond
235 ((not (nndoc-possibly-change-buffer group server))
236 (nnheader-report 'nndoc "No such file or buffer: %s"
237 nndoc-address))
238 (dont-check
239 (nnheader-report 'nndoc "Selected group %s" group)
240 t)
241 ((zerop (setq number (length nndoc-dissection-alist)))
242 (nndoc-close-group group)
243 (nnheader-report 'nndoc "No articles in group %s" group))
244 (t
245 (nnheader-insert "211 %d %d %d %s\n" number 1 number group)))))
246
247(deffoo nndoc-request-type (group &optional article)
248 (cond ((not article) 'unknown)
249 (nndoc-post-type nndoc-post-type)
250 (t 'unknown)))
251
252(deffoo nndoc-close-group (group &optional server)
253 (nndoc-possibly-change-buffer group server)
254 (and nndoc-current-buffer
255 (buffer-name nndoc-current-buffer)
256 (kill-buffer nndoc-current-buffer))
257 (setq nndoc-group-alist (delq (assoc group nndoc-group-alist)
258 nndoc-group-alist))
259 (setq nndoc-current-buffer nil)
260 (nnoo-close-server 'nndoc server)
261 (setq nndoc-dissection-alist nil)
262 t)
263
264(deffoo nndoc-request-list (&optional server)
265 nil)
266
267(deffoo nndoc-request-newgroups (date &optional server)
268 nil)
269
270(deffoo nndoc-request-list-newsgroups (&optional server)
271 nil)
272
273\f
274;;; Internal functions.
275
276(defun nndoc-possibly-change-buffer (group source)
277 (let (buf)
278 (cond
279 ;; The current buffer is this group's buffer.
280 ((and nndoc-current-buffer
281 (buffer-name nndoc-current-buffer)
282 (eq nndoc-current-buffer
283 (setq buf (cdr (assoc group nndoc-group-alist))))))
284 ;; We change buffers by taking an old from the group alist.
285 ;; `source' is either a string (a file name) or a buffer object.
286 (buf
287 (setq nndoc-current-buffer buf))
288 ;; It's a totally new group.
289 ((or (and (bufferp nndoc-address)
290 (buffer-name nndoc-address))
291 (and (stringp nndoc-address)
292 (file-exists-p nndoc-address)
293 (not (file-directory-p nndoc-address))))
294 (push (cons group (setq nndoc-current-buffer
295 (get-buffer-create
296 (concat " *nndoc " group "*"))))
297 nndoc-group-alist)
298 (setq nndoc-dissection-alist nil)
299 (save-excursion
300 (set-buffer nndoc-current-buffer)
eec82323
LMI
301 (erase-buffer)
302 (if (stringp nndoc-address)
303 (nnheader-insert-file-contents nndoc-address)
6748645f
LMI
304 (insert-buffer-substring nndoc-address))
305 (run-hooks 'nndoc-open-document-hook))))
eec82323
LMI
306 ;; Initialize the nndoc structures according to this new document.
307 (when (and nndoc-current-buffer
308 (not nndoc-dissection-alist))
309 (save-excursion
310 (set-buffer nndoc-current-buffer)
311 (nndoc-set-delims)
6748645f
LMI
312 (if (eq nndoc-article-type 'mime-parts)
313 (nndoc-dissect-mime-parts)
314 (nndoc-dissect-buffer))))
eec82323
LMI
315 (unless nndoc-current-buffer
316 (nndoc-close-server))
317 ;; Return whether we managed to select a file.
318 nndoc-current-buffer))
319
320;;;
321;;; Deciding what document type we have
322;;;
323
324(defun nndoc-set-delims ()
325 "Set the nndoc delimiter variables according to the type of the document."
326 (let ((vars '(nndoc-file-begin
327 nndoc-first-article
6748645f
LMI
328 nndoc-article-begin-function
329 nndoc-head-begin nndoc-head-end
eec82323
LMI
330 nndoc-file-end nndoc-article-begin
331 nndoc-body-begin nndoc-body-end-function nndoc-body-end
332 nndoc-prepare-body-function nndoc-article-transform-function
333 nndoc-generate-head-function nndoc-body-begin-function
334 nndoc-head-begin-function)))
335 (while vars
336 (set (pop vars) nil)))
337 (let (defs)
338 ;; Guess away until we find the real file type.
339 (while (assq 'guess (setq defs (cdr (assq nndoc-article-type
340 nndoc-type-alist))))
341 (setq nndoc-article-type (nndoc-guess-type nndoc-article-type)))
342 ;; Set the nndoc variables.
343 (while defs
344 (set (intern (format "nndoc-%s" (caar defs)))
345 (cdr (pop defs))))))
346
347(defun nndoc-guess-type (subtype)
348 (let ((alist nndoc-type-alist)
349 results result entry)
350 (while (and (not result)
351 (setq entry (pop alist)))
352 (when (memq subtype (or (cdr (assq 'subtype entry)) '(guess)))
353 (goto-char (point-min))
16409b0b
GM
354 ;; Remove blank lines.
355 (while (eq (following-char) ?\n)
356 (delete-char 1))
eec82323
LMI
357 (when (numberp (setq result (funcall (intern
358 (format "nndoc-%s-type-p"
359 (car entry))))))
360 (push (cons result entry) results)
361 (setq result nil))))
362 (unless (or result results)
363 (error "Document is not of any recognized type"))
364 (if result
365 (car entry)
6748645f 366 (cadar (sort results 'car-less-than-car)))))
eec82323
LMI
367
368;;;
369;;; Built-in type predicates and functions
370;;;
371
372(defun nndoc-mbox-type-p ()
373 (when (looking-at message-unix-mail-delimiter)
374 t))
375
376(defun nndoc-mbox-article-begin ()
377 (when (re-search-forward (concat "^" message-unix-mail-delimiter) nil t)
378 (goto-char (match-beginning 0))))
379
380(defun nndoc-mbox-body-end ()
381 (let ((beg (point))
382 len end)
383 (when
384 (save-excursion
385 (and (re-search-backward
386 (concat "^" message-unix-mail-delimiter) nil t)
387 (setq end (point))
388 (search-forward "\n\n" beg t)
389 (re-search-backward
390 "^Content-Length:[ \t]*\\([0-9]+\\) *$" end t)
391 (setq len (string-to-int (match-string 1)))
392 (search-forward "\n\n" beg t)
393 (unless (= (setq len (+ (point) len)) (point-max))
394 (and (< len (point-max))
395 (goto-char len)
396 (looking-at message-unix-mail-delimiter)))))
397 (goto-char len))))
398
399(defun nndoc-mmdf-type-p ()
400 (when (looking-at "\^A\^A\^A\^A$")
401 t))
402
403(defun nndoc-news-type-p ()
404 (when (looking-at "^Path:.*\n")
405 t))
406
407(defun nndoc-rnews-type-p ()
408 (when (looking-at "#! *rnews")
409 t))
410
411(defun nndoc-rnews-body-end ()
412 (and (re-search-backward nndoc-article-begin nil t)
413 (forward-line 1)
414 (goto-char (+ (point) (string-to-int (match-string 1))))))
415
416(defun nndoc-babyl-type-p ()
417 (when (re-search-forward "\^_\^L *\n" nil t)
418 t))
419
420(defun nndoc-babyl-body-begin ()
421 (re-search-forward "^\n" nil t)
6748645f 422 (when (looking-at "\\*\\*\\* EOOH \\*\\*\\*")
eec82323
LMI
423 (let ((next (or (save-excursion
424 (re-search-forward nndoc-article-begin nil t))
425 (point-max))))
426 (unless (re-search-forward "^\n" next t)
427 (goto-char next)
428 (forward-line -1)
429 (insert "\n")
430 (forward-line -1)))))
431
432(defun nndoc-babyl-head-begin ()
433 (when (re-search-forward "^[0-9].*\n" nil t)
6748645f 434 (when (looking-at "\\*\\*\\* EOOH \\*\\*\\*")
eec82323
LMI
435 (forward-line 1))
436 t))
437
438(defun nndoc-forward-type-p ()
16409b0b
GM
439 (when (and (re-search-forward "^-+ \\(Start of \\)?forwarded message.*\n+"
440 nil t)
eec82323
LMI
441 (not (re-search-forward "^Subject:.*digest" nil t))
442 (not (re-search-backward "^From:" nil t 2))
443 (not (re-search-forward "^From:" nil t 2)))
444 t))
445
446(defun nndoc-rfc934-type-p ()
447 (when (and (re-search-forward "^-+ Start of forwarded.*\n+" nil t)
448 (not (re-search-forward "^Subject:.*digest" nil t))
449 (not (re-search-backward "^From:" nil t 2))
450 (not (re-search-forward "^From:" nil t 2)))
451 t))
452
453(defun nndoc-rfc822-forward-type-p ()
454 (save-restriction
455 (message-narrow-to-head)
456 (when (re-search-forward "^Content-Type: *message/rfc822" nil t)
457 t)))
458
459(defun nndoc-rfc822-forward-body-end-function ()
460 (goto-char (point-max)))
461
6748645f
LMI
462(defun nndoc-mime-parts-type-p ()
463 (let ((case-fold-search t)
464 (limit (search-forward "\n\n" nil t)))
465 (goto-char (point-min))
466 (when (and limit
16409b0b
GM
467 (re-search-forward
468 (concat "\
469^Content-Type:[ \t]*multipart/[a-z]+ *; *\\(\\(\n[ \t]\\)?.*;\\)*"
470 "\\(\n[ \t]\\)?[ \t]*boundary=\"?[^\"\n]*[^\" \t\n]")
471 limit t))
6748645f
LMI
472 t)))
473
474(defun nndoc-transform-mime-parts (article)
16409b0b
GM
475 (let* ((entry (cdr (assq article nndoc-dissection-alist)))
476 (headers (nth 5 entry)))
477 (when headers
6748645f 478 (goto-char (point-min))
16409b0b
GM
479 (insert headers))))
480
481(defun nndoc-generate-mime-parts-head (article)
482 (let* ((entry (cdr (assq article nndoc-dissection-alist)))
483 (headers (nth 6 entry)))
484 (save-restriction
485 (narrow-to-region (point) (point))
486 (insert-buffer-substring
487 nndoc-current-buffer (car entry) (nth 1 entry))
488 (goto-char (point-max)))
489 (when headers
490 (insert headers))))
6748645f 491
eec82323
LMI
492(defun nndoc-clari-briefs-type-p ()
493 (when (let ((case-fold-search nil))
494 (re-search-forward "^\t[^a-z]+ ([^a-z]+) --" nil t))
495 t))
496
497(defun nndoc-transform-clari-briefs (article)
498 (goto-char (point-min))
499 (when (looking-at " *\\*\\(.*\\)\n")
500 (replace-match "" t t))
501 (nndoc-generate-clari-briefs-head article))
502
503(defun nndoc-generate-clari-briefs-head (article)
504 (let ((entry (cdr (assq article nndoc-dissection-alist)))
505 subject from)
506 (save-excursion
507 (set-buffer nndoc-current-buffer)
508 (save-restriction
509 (narrow-to-region (car entry) (nth 3 entry))
510 (goto-char (point-min))
511 (when (looking-at " *\\*\\(.*\\)$")
512 (setq subject (match-string 1))
513 (when (string-match "[ \t]+$" subject)
514 (setq subject (substring subject 0 (match-beginning 0)))))
515 (when
516 (let ((case-fold-search nil))
517 (re-search-forward
518 "^\t\\([^a-z]+\\(,[^(]+\\)? ([^a-z]+)\\) --" nil t))
519 (setq from (match-string 1)))))
520 (insert "From: " "clari@clari.net (" (or from "unknown") ")"
521 "\nSubject: " (or subject "(no subject)") "\n")))
522
16409b0b 523
eec82323
LMI
524(defun nndoc-mime-digest-type-p ()
525 (let ((case-fold-search t)
526 boundary-id b-delimiter entry)
527 (when (and
528 (re-search-forward
529 (concat "^Content-Type: *multipart/digest;[ \t\n]*[ \t]"
6748645f 530 "boundary=\"?\\([^\"\n]*[^\" \t\n]\\)")
eec82323
LMI
531 nil t)
532 (match-beginning 1))
533 (setq boundary-id (match-string 1)
16409b0b 534 b-delimiter (concat "\n--" boundary-id "[ \t]*$"))
eec82323
LMI
535 (setq entry (assq 'mime-digest nndoc-type-alist))
536 (setcdr entry
537 (list
16409b0b 538 (cons 'head-begin "^ ?\n")
eec82323
LMI
539 (cons 'head-end "^ ?$")
540 (cons 'body-begin "^ ?\n")
541 (cons 'article-begin b-delimiter)
542 (cons 'body-end-function 'nndoc-digest-body-end)
543 (cons 'file-end (concat "\n--" boundary-id "--[ \t]*$"))))
544 t)))
545
546(defun nndoc-standard-digest-type-p ()
547 (when (and (re-search-forward (concat "^" (make-string 70 ?-) "\n\n") nil t)
548 (re-search-forward
549 (concat "\n\n" (make-string 30 ?-) "\n\n") nil t))
550 t))
551
552(defun nndoc-digest-body-end ()
553 (and (re-search-forward nndoc-article-begin nil t)
554 (goto-char (match-beginning 0))))
555
556(defun nndoc-slack-digest-type-p ()
557 0)
558
559(defun nndoc-lanl-gov-announce-type-p ()
560 (when (let ((case-fold-search nil))
561 (re-search-forward "^\\\\\\\\\nPaper: [a-z-]+/[0-9]+" nil t))
562 t))
563
564(defun nndoc-transform-lanl-gov-announce (article)
565 (goto-char (point-max))
566 (when (re-search-backward "^\\\\\\\\ +(\\([^ ]*\\) , *\\([^ ]*\\))" nil t)
16409b0b 567 (replace-match "\n\nGet it at \\1 (\\2)" t nil)))
eec82323
LMI
568
569(defun nndoc-generate-lanl-gov-head (article)
570 (let ((entry (cdr (assq article nndoc-dissection-alist)))
571 (e-mail "no address given")
572 subject from)
573 (save-excursion
574 (set-buffer nndoc-current-buffer)
575 (save-restriction
576 (narrow-to-region (car entry) (nth 1 entry))
577 (goto-char (point-min))
578 (when (looking-at "^Paper.*: \\([a-z-]+/[0-9]+\\)")
579 (setq subject (concat " (" (match-string 1) ")"))
580 (when (re-search-forward "^From: \\([^ ]+\\)" nil t)
581 (setq e-mail (match-string 1)))
582 (when (re-search-forward "^Title: \\([^\f]*\\)\nAuthors?: \\(.*\\)"
583 nil t)
584 (setq subject (concat (match-string 1) subject))
16409b0b 585 (setq from (concat (match-string 2) " <" e-mail ">"))))))
eec82323
LMI
586 (while (and from (string-match "(\[^)\]*)" from))
587 (setq from (replace-match "" t t from)))
588 (insert "From: " (or from "unknown")
589 "\nSubject: " (or subject "(no subject)") "\n")))
590
16409b0b
GM
591(defun nndoc-nsmail-type-p ()
592 (when (looking-at "From - ")
593 t))
594
595(defun nndoc-outlook-article-begin ()
596 (prog1 (re-search-forward "From:\\|Received:" nil t)
597 (goto-char (match-beginning 0))))
598
599(defun nndoc-outlook-type-p ()
600 ;; FIXME: Is JMF the magic of outlook mailbox? -- ShengHuo.
601 (looking-at "JMF"))
602
6748645f
LMI
603(deffoo nndoc-request-accept-article (group &optional server last)
604 nil)
605
eec82323 606
eec82323
LMI
607;;;
608;;; Functions for dissecting the documents
609;;;
610
611(defun nndoc-search (regexp)
612 (prog1
613 (re-search-forward regexp nil t)
614 (beginning-of-line)))
615
616(defun nndoc-dissect-buffer ()
617 "Go through the document and partition it into heads/bodies/articles."
618 (let ((i 0)
619 (first t)
620 head-begin head-end body-begin body-end)
621 (setq nndoc-dissection-alist nil)
622 (save-excursion
623 (set-buffer nndoc-current-buffer)
624 (goto-char (point-min))
16409b0b
GM
625 ;; Remove blank lines.
626 (while (eq (following-char) ?\n)
627 (delete-char 1))
eec82323
LMI
628 ;; Find the beginning of the file.
629 (when nndoc-file-begin
630 (nndoc-search nndoc-file-begin))
631 ;; Go through the file.
632 (while (if (and first nndoc-first-article)
633 (nndoc-search nndoc-first-article)
634 (nndoc-article-begin))
635 (setq first nil)
636 (cond (nndoc-head-begin-function
637 (funcall nndoc-head-begin-function))
638 (nndoc-head-begin
639 (nndoc-search nndoc-head-begin)))
6748645f 640 (if (or (eobp)
eec82323
LMI
641 (and nndoc-file-end
642 (looking-at nndoc-file-end)))
643 (goto-char (point-max))
644 (setq head-begin (point))
645 (nndoc-search (or nndoc-head-end "^$"))
646 (setq head-end (point))
647 (if nndoc-body-begin-function
648 (funcall nndoc-body-begin-function)
649 (nndoc-search (or nndoc-body-begin "^\n")))
650 (setq body-begin (point))
651 (or (and nndoc-body-end-function
652 (funcall nndoc-body-end-function))
653 (and nndoc-body-end
654 (nndoc-search nndoc-body-end))
655 (nndoc-article-begin)
656 (progn
657 (goto-char (point-max))
658 (when nndoc-file-end
659 (and (re-search-backward nndoc-file-end nil t)
660 (beginning-of-line)))))
661 (setq body-end (point))
662 (push (list (incf i) head-begin head-end body-begin body-end
663 (count-lines body-begin body-end))
664 nndoc-dissection-alist))))))
665
666(defun nndoc-article-begin ()
667 (if nndoc-article-begin-function
668 (funcall nndoc-article-begin-function)
669 (ignore-errors
670 (nndoc-search nndoc-article-begin))))
671
672(defun nndoc-unquote-dashes ()
673 "Unquote quoted non-separators in digests."
674 (while (re-search-forward "^- -"nil t)
675 (replace-match "-" t t)))
676
6748645f
LMI
677;; Against compiler warnings.
678(defvar nndoc-mime-split-ordinal)
679
680(defun nndoc-dissect-mime-parts ()
681 "Go through a MIME composite article and partition it into sub-articles.
682When a MIME entity contains sub-entities, dissection produces one article for
683the header of this entity, and one article per sub-entity."
684 (setq nndoc-dissection-alist nil
685 nndoc-mime-split-ordinal 0)
686 (save-excursion
687 (set-buffer nndoc-current-buffer)
16409b0b
GM
688 (nndoc-dissect-mime-parts-sub (point-min) (point-max) nil nil nil)))
689
690(defun nndoc-dissect-mime-parts-sub (head-begin body-end article-insert
691 position parent)
692 "Dissect an entity, within a composite MIME message.
693The complete message or MIME entity extends from HEAD-BEGIN to BODY-END.
694ARTICLE-INSERT should be added at beginning for generating a full article.
6748645f
LMI
695The string POSITION holds a dotted decimal representation of the article
696position in the hierarchical structure, it is nil for the outer entity.
16409b0b
GM
697PARENT is the message-ID of the parent summary line, or nil for none."
698 (let ((case-fold-search t)
699 (message-id (nnmail-message-id))
700 head-end body-begin summary-insert message-rfc822 multipart-any
701 subject content-type type subtype boundary-regexp)
702 ;; Gracefully handle a missing body.
703 (goto-char head-begin)
2e36fdce
DL
704 (if (or (and (eq (char-after) ?\n) (or (forward-char 1) t))
705 (search-forward "\n\n" body-end t))
16409b0b
GM
706 (setq head-end (1- (point))
707 body-begin (point))
708 (setq head-end body-end
709 body-begin body-end))
710 (narrow-to-region head-begin head-end)
711 ;; Save MIME attributes.
712 (goto-char head-begin)
713 (setq content-type (message-fetch-field "Content-Type"))
714 (when content-type
715 (when (string-match
716 "^ *\\([^ \t\n/;]+\\)/\\([^ \t\n/;]+\\)" content-type)
717 (setq type (downcase (match-string 1 content-type))
718 subtype (downcase (match-string 2 content-type))
719 message-rfc822 (and (string= type "message")
720 (string= subtype "rfc822"))
721 multipart-any (string= type "multipart")))
722 (when (string-match ";[ \t\n]*name=\\([^ \t\n;]+\\)" content-type)
723 (setq subject (match-string 1 content-type)))
724 (when (string-match "boundary=\"?\\([^\"\n]*[^\" \t\n]\\)" content-type)
725 (setq boundary-regexp (concat "^--"
726 (regexp-quote
727 (match-string 1 content-type))
728 "\\(--\\)?[ \t]*\n"))))
729 (unless subject
730 (when (or multipart-any (not article-insert))
731 (setq subject (message-fetch-field "Subject"))))
732 (unless type
733 (setq type "text"
734 subtype "plain"))
735 ;; Prepare the article and summary inserts.
736 (unless article-insert
737 (setq article-insert (buffer-substring (point-min) (point-max))
738 head-end head-begin))
739 (setq summary-insert article-insert)
740 ;; - summary Subject.
741 (setq summary-insert
742 (let ((line (concat "Subject: <" position
743 (and position multipart-any ".")
744 (and multipart-any "*")
745 (and (or position multipart-any) " ")
746 (cond ((string= subtype "plain") type)
747 ((string= subtype "basic") type)
748 (t subtype))
749 ">"
750 (and subject " ")
751 subject
752 "\n")))
753 (if (string-match "Subject:.*\n\\([ \t].*\n\\)*" summary-insert)
754 (replace-match line t t summary-insert)
755 (concat summary-insert line))))
756 ;; - summary Message-ID.
757 (setq summary-insert
758 (let ((line (concat "Message-ID: " message-id "\n")))
759 (if (string-match "Message-ID:.*\n\\([ \t].*\n\\)*" summary-insert)
760 (replace-match line t t summary-insert)
761 (concat summary-insert line))))
762 ;; - summary References.
763 (when parent
764 (setq summary-insert
765 (let ((line (concat "References: " parent "\n")))
766 (if (string-match "References:.*\n\\([ \t].*\n\\)*"
767 summary-insert)
768 (replace-match line t t summary-insert)
769 (concat summary-insert line)))))
770 ;; Generate dissection information for this entity.
771 (push (list (incf nndoc-mime-split-ordinal)
772 head-begin head-end body-begin body-end
773 (count-lines body-begin body-end)
774 article-insert summary-insert)
775 nndoc-dissection-alist)
776 ;; Recurse for all sub-entities, if any.
777 (widen)
778 (cond
779 (message-rfc822
780 (save-excursion
781 (nndoc-dissect-mime-parts-sub body-begin body-end nil
782 position message-id)))
783 ((and multipart-any boundary-regexp)
784 (let ((part-counter 0)
785 part-begin part-end eof-flag)
786 (while (string-match "\
787^\\(Lines\\|Content-\\(Type\\|Transfer-Encoding\\|Disposition\\)\\):.*\n\\([ \t].*\n\\)*"
788 article-insert)
789 (setq article-insert (replace-match "" t t article-insert)))
790 (let ((case-fold-search nil))
791 (goto-char body-begin)
792 (setq eof-flag (not (re-search-forward boundary-regexp body-end t)))
6748645f 793 (while (not eof-flag)
16409b0b
GM
794 (setq part-begin (point))
795 (cond ((re-search-forward boundary-regexp body-end t)
6748645f
LMI
796 (or (not (match-string 1))
797 (string= (match-string 1) "")
798 (setq eof-flag t))
799 (forward-line -1)
16409b0b 800 (setq part-end (point))
6748645f 801 (forward-line 1))
16409b0b 802 (t (setq part-end body-end
6748645f 803 eof-flag t)))
16409b0b
GM
804 (save-excursion
805 (nndoc-dissect-mime-parts-sub
806 part-begin part-end article-insert
807 (concat position
808 (and position ".")
809 (format "%d" (incf part-counter)))
810 message-id)))))))))
6748645f 811
eec82323
LMI
812;;;###autoload
813(defun nndoc-add-type (definition &optional position)
814 "Add document DEFINITION to the list of nndoc document definitions.
815If POSITION is nil or `last', the definition will be added
816as the last checked definition, if t or `first', add as the
817first definition, and if any other symbol, add after that
818symbol in the alist."
819 ;; First remove any old instances.
6748645f 820 (gnus-pull (car definition) nndoc-type-alist)
eec82323
LMI
821 ;; Then enter the new definition in the proper place.
822 (cond
823 ((or (null position) (eq position 'last))
824 (setq nndoc-type-alist (nconc nndoc-type-alist (list definition))))
825 ((or (eq position t) (eq position 'first))
826 (push definition nndoc-type-alist))
827 (t
828 (let ((list (memq (assq position nndoc-type-alist)
829 nndoc-type-alist)))
830 (unless list
831 (error "No such position: %s" position))
832 (setcdr list (cons definition (cdr list)))))))
833
834(provide 'nndoc)
835
836;;; nndoc.el ends here