Remove unnecessary leading-star in docstrings.
[emacs.git] / lisp / gnus / nndoc.el
blob70e301b5b00fb92becefa78f488f219e99270704
1 ;;; nndoc.el --- single file access for Gnus
3 ;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003,
4 ;; 2004, 2005 Free Software Foundation, Inc.
6 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
7 ;; Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
8 ;; Keywords: news
10 ;; This file is part of GNU Emacs.
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
15 ;; any later version.
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to the
24 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 ;; Boston, MA 02110-1301, USA.
27 ;;; Commentary:
29 ;; For Outlook mail boxes format, see http://mbx2mbox.sourceforge.net/
31 ;;; Code:
33 (require 'nnheader)
34 (require 'message)
35 (require 'nnmail)
36 (require 'nnoo)
37 (require 'gnus-util)
38 (require 'mm-util)
39 (eval-when-compile (require 'cl))
41 (nnoo-declare nndoc)
43 (defvoo nndoc-article-type 'guess
44 "*Type of the file.
45 One of `mbox', `babyl', `digest', `news', `rnews', `mmdf', `forward',
46 `rfc934', `rfc822-forward', `mime-parts', `standard-digest',
47 `slack-digest', `clari-briefs', `nsmail', `outlook', `oe-dbx',
48 `mailman', `exim-bounce', or `guess'.")
50 (defvoo nndoc-post-type 'mail
51 "*Whether the nndoc group is `mail' or `post'.")
53 (defvoo nndoc-open-document-hook 'nnheader-ms-strip-cr
54 "Hook run after opening a document.
55 The default function removes all trailing carriage returns
56 from the document.")
58 (defvar nndoc-type-alist
59 `((mmdf
60 (article-begin . "^\^A\^A\^A\^A\n")
61 (body-end . "^\^A\^A\^A\^A\n"))
62 (mime-digest
63 (article-begin . "")
64 (head-begin . "^ ?\n")
65 (head-end . "^ ?$")
66 (body-end . "")
67 (file-end . "")
68 (subtype digest guess))
69 (mime-parts
70 (generate-head-function . nndoc-generate-mime-parts-head)
71 (article-transform-function . nndoc-transform-mime-parts))
72 (nsmail
73 (article-begin . "^From - "))
74 (news
75 (article-begin . "^Path:"))
76 (rnews
77 (article-begin . "^#! *rnews +\\([0-9]+\\) *\n")
78 (body-end-function . nndoc-rnews-body-end))
79 (mbox
80 (article-begin-function . nndoc-mbox-article-begin)
81 (body-end-function . nndoc-mbox-body-end))
82 (babyl
83 (article-begin . "\^_\^L *\n")
84 (body-end . "\^_")
85 (body-begin-function . nndoc-babyl-body-begin)
86 (head-begin-function . nndoc-babyl-head-begin))
87 (exim-bounce
88 (article-begin . "^------ This is a copy of the message, including all the headers. ------\n\n")
89 (body-end-function . nndoc-exim-bounce-body-end-function))
90 (rfc934
91 (article-begin . "^--.*\n+")
92 (body-end . "^--.*$")
93 (prepare-body-function . nndoc-unquote-dashes))
94 (mailman
95 (article-begin . "^--__--__--\n\nMessage:")
96 (body-end . "^--__--__--$")
97 (prepare-body-function . nndoc-unquote-dashes))
98 (clari-briefs
99 (article-begin . "^ \\*")
100 (body-end . "^\t------*[ \t]^*\n^ \\*")
101 (body-begin . "^\t")
102 (head-end . "^\t")
103 (generate-head-function . nndoc-generate-clari-briefs-head)
104 (article-transform-function . nndoc-transform-clari-briefs))
106 (standard-digest
107 (first-article . ,(concat "^" (make-string 70 ?-) "\n *\n+"))
108 (article-begin . ,(concat "^\n" (make-string 30 ?-) "\n *\n+"))
109 (prepare-body-function . nndoc-unquote-dashes)
110 (body-end-function . nndoc-digest-body-end)
111 (head-end . "^ *$")
112 (body-begin . "^ *\n")
113 (file-end . "^End of .*digest.*[0-9].*\n\\*\\*\\|^End of.*Digest *$")
114 (subtype digest guess))
115 (slack-digest
116 (article-begin . "^------------------------------*[\n \t]+")
117 (head-end . "^ ?$")
118 (body-end-function . nndoc-digest-body-end)
119 (body-begin . "^ ?$")
120 (file-end . "^End of")
121 (prepare-body-function . nndoc-unquote-dashes)
122 (subtype digest guess))
123 (lanl-gov-announce
124 (article-begin . "^\\\\\\\\\n")
125 (head-begin . "^Paper.*:")
126 (head-end . "\\(^\\\\\\\\.*\n\\|-----------------\\)")
127 (body-begin . "")
128 (body-end . "\\(-------------------------------------------------\\|%-%-%-%-%-%-%-%-%-%-%-%-%-%-\\|%%--%%--%%--%%--%%--%%--%%--%%--\\|%%%---%%%---%%%---%%%---\\)")
129 (file-end . "\\(^Title: Recent Seminal\\|%%%---%%%---%%%---%%%---\\)")
130 (generate-head-function . nndoc-generate-lanl-gov-head)
131 (article-transform-function . nndoc-transform-lanl-gov-announce)
132 (subtype preprints guess))
133 (rfc822-forward
134 (article-begin . "^\n+")
135 (body-end-function . nndoc-rfc822-forward-body-end-function)
136 (generate-head-function . nndoc-rfc822-forward-generate-head)
137 (generate-article-function . nndoc-rfc822-forward-generate-article))
138 (outlook
139 (article-begin-function . nndoc-outlook-article-begin)
140 (body-end . "\0"))
141 (oe-dbx ;; Outlook Express DBX format
142 (dissection-function . nndoc-oe-dbx-dissection)
143 (generate-head-function . nndoc-oe-dbx-generate-head)
144 (generate-article-function . nndoc-oe-dbx-generate-article))
145 (forward
146 (article-begin . "^-+ \\(Start of \\)?forwarded message.*\n+")
147 (body-end . "^-+ End \\(of \\)?forwarded message.*$")
148 (prepare-body-function . nndoc-unquote-dashes))
149 (mail-in-mail ;; Wild guess on mailer daemon's messages or others
150 (article-begin-function . nndoc-mail-in-mail-article-begin))
151 (guess
152 (guess . t)
153 (subtype nil))
154 (digest
155 (guess . t)
156 (subtype nil))
157 (preprints
158 (guess . t)
159 (subtype nil))))
161 (defvar nndoc-binary-file-names ".[Dd][Bb][Xx]$"
162 "Regexp for binary nndoc file names.")
165 (defvoo nndoc-file-begin nil)
166 (defvoo nndoc-first-article nil)
167 (defvoo nndoc-article-begin nil)
168 (defvoo nndoc-head-begin nil)
169 (defvoo nndoc-head-end nil)
170 (defvoo nndoc-file-end nil)
171 (defvoo nndoc-body-begin nil)
172 (defvoo nndoc-body-end-function nil)
173 (defvoo nndoc-body-begin-function nil)
174 (defvoo nndoc-head-begin-function nil)
175 (defvoo nndoc-body-end nil)
176 ;; nndoc-dissection-alist is a list of sublists. Each sublist holds the
177 ;; following items. ARTICLE acts as the association key and is an ordinal
178 ;; starting at 1. HEAD-BEGIN [0], HEAD-END [1], BODY-BEGIN [2] and BODY-END
179 ;; [3] are positions in the `nndoc' buffer. LINE-COUNT [4] is a count of
180 ;; lines in the body. For MIME dissections only, ARTICLE-INSERT [5] and
181 ;; SUMMARY-INSERT [6] give headers to insert for full article or summary line
182 ;; generation, respectively. Other headers usually follow directly from the
183 ;; buffer. Value `nil' means no insert.
184 (defvoo nndoc-dissection-alist nil)
185 (defvoo nndoc-prepare-body-function nil)
186 (defvoo nndoc-generate-head-function nil)
187 (defvoo nndoc-article-transform-function nil)
188 (defvoo nndoc-article-begin-function nil)
189 (defvoo nndoc-generate-article-function nil)
190 (defvoo nndoc-dissection-function nil)
192 (defvoo nndoc-status-string "")
193 (defvoo nndoc-group-alist nil)
194 (defvoo nndoc-current-buffer nil
195 "Current nndoc news buffer.")
196 (defvoo nndoc-address nil)
198 (defconst nndoc-version "nndoc 1.0"
199 "nndoc version.")
203 ;;; Interface functions
205 (nnoo-define-basics nndoc)
207 (deffoo nndoc-retrieve-headers (articles &optional newsgroup server fetch-old)
208 (when (nndoc-possibly-change-buffer newsgroup server)
209 (save-excursion
210 (set-buffer nntp-server-buffer)
211 (erase-buffer)
212 (let (article entry)
213 (if (stringp (car articles))
214 'headers
215 (while articles
216 (when (setq entry (cdr (assq (setq article (pop articles))
217 nndoc-dissection-alist)))
218 (insert (format "221 %d Article retrieved.\n" article))
219 (if nndoc-generate-head-function
220 (funcall nndoc-generate-head-function article)
221 (insert-buffer-substring
222 nndoc-current-buffer (car entry) (nth 1 entry)))
223 (goto-char (point-max))
224 (unless (eq (char-after (1- (point))) ?\n)
225 (insert "\n"))
226 (insert (format "Lines: %d\n" (nth 4 entry)))
227 (insert ".\n")))
229 (nnheader-fold-continuation-lines)
230 'headers)))))
232 (deffoo nndoc-request-article (article &optional newsgroup server buffer)
233 (nndoc-possibly-change-buffer newsgroup server)
234 (save-excursion
235 (let ((buffer (or buffer nntp-server-buffer))
236 (entry (cdr (assq article nndoc-dissection-alist)))
237 beg)
238 (set-buffer buffer)
239 (erase-buffer)
240 (when entry
241 (cond
242 ((stringp article) nil)
243 (nndoc-generate-article-function
244 (funcall nndoc-generate-article-function article))
246 (insert-buffer-substring
247 nndoc-current-buffer (car entry) (nth 1 entry))
248 (insert "\n")
249 (setq beg (point))
250 (insert-buffer-substring
251 nndoc-current-buffer (nth 2 entry) (nth 3 entry))
252 (goto-char beg)
253 (when nndoc-prepare-body-function
254 (funcall nndoc-prepare-body-function))
255 (when nndoc-article-transform-function
256 (funcall nndoc-article-transform-function article))
257 t))))))
259 (deffoo nndoc-request-group (group &optional server dont-check)
260 "Select news GROUP."
261 (let (number)
262 (cond
263 ((not (nndoc-possibly-change-buffer group server))
264 (nnheader-report 'nndoc "No such file or buffer: %s"
265 nndoc-address))
266 (dont-check
267 (nnheader-report 'nndoc "Selected group %s" group)
269 ((zerop (setq number (length nndoc-dissection-alist)))
270 (nndoc-close-group group)
271 (nnheader-report 'nndoc "No articles in group %s" group))
273 (nnheader-insert "211 %d %d %d %s\n" number 1 number group)))))
275 (deffoo nndoc-request-type (group &optional article)
276 (cond ((not article) 'unknown)
277 (nndoc-post-type nndoc-post-type)
278 (t 'unknown)))
280 (deffoo nndoc-close-group (group &optional server)
281 (nndoc-possibly-change-buffer group server)
282 (and nndoc-current-buffer
283 (buffer-name nndoc-current-buffer)
284 (kill-buffer nndoc-current-buffer))
285 (setq nndoc-group-alist (delq (assoc group nndoc-group-alist)
286 nndoc-group-alist))
287 (setq nndoc-current-buffer nil)
288 (nnoo-close-server 'nndoc server)
289 (setq nndoc-dissection-alist nil)
292 (deffoo nndoc-request-list (&optional server)
293 nil)
295 (deffoo nndoc-request-newgroups (date &optional server)
296 nil)
298 (deffoo nndoc-request-list-newsgroups (&optional server)
299 nil)
302 ;;; Internal functions.
304 (defun nndoc-possibly-change-buffer (group source)
305 (let (buf)
306 (cond
307 ;; The current buffer is this group's buffer.
308 ((and nndoc-current-buffer
309 (buffer-name nndoc-current-buffer)
310 (eq nndoc-current-buffer
311 (setq buf (cdr (assoc group nndoc-group-alist))))))
312 ;; We change buffers by taking an old from the group alist.
313 ;; `source' is either a string (a file name) or a buffer object.
314 (buf
315 (setq nndoc-current-buffer buf))
316 ;; It's a totally new group.
317 ((or (and (bufferp nndoc-address)
318 (buffer-name nndoc-address))
319 (and (stringp nndoc-address)
320 (file-exists-p nndoc-address)
321 (not (file-directory-p nndoc-address))))
322 (push (cons group (setq nndoc-current-buffer
323 (get-buffer-create
324 (concat " *nndoc " group "*"))))
325 nndoc-group-alist)
326 (setq nndoc-dissection-alist nil)
327 (save-excursion
328 (set-buffer nndoc-current-buffer)
329 (erase-buffer)
330 (if (and (stringp nndoc-address)
331 (string-match nndoc-binary-file-names nndoc-address))
332 (let ((coding-system-for-read 'binary))
333 (mm-insert-file-contents nndoc-address))
334 (if (stringp nndoc-address)
335 (nnheader-insert-file-contents nndoc-address)
336 (insert-buffer-substring nndoc-address))
337 (run-hooks 'nndoc-open-document-hook)))))
338 ;; Initialize the nndoc structures according to this new document.
339 (when (and nndoc-current-buffer
340 (not nndoc-dissection-alist))
341 (save-excursion
342 (set-buffer nndoc-current-buffer)
343 (nndoc-set-delims)
344 (if (eq nndoc-article-type 'mime-parts)
345 (nndoc-dissect-mime-parts)
346 (nndoc-dissect-buffer))))
347 (unless nndoc-current-buffer
348 (nndoc-close-server))
349 ;; Return whether we managed to select a file.
350 nndoc-current-buffer))
353 ;;; Deciding what document type we have
356 (defun nndoc-set-delims ()
357 "Set the nndoc delimiter variables according to the type of the document."
358 (let ((vars '(nndoc-file-begin
359 nndoc-first-article
360 nndoc-article-begin-function
361 nndoc-head-begin nndoc-head-end
362 nndoc-file-end nndoc-article-begin
363 nndoc-body-begin nndoc-body-end-function nndoc-body-end
364 nndoc-prepare-body-function nndoc-article-transform-function
365 nndoc-generate-head-function nndoc-body-begin-function
366 nndoc-head-begin-function
367 nndoc-generate-article-function
368 nndoc-dissection-function)))
369 (while vars
370 (set (pop vars) nil)))
371 (let (defs)
372 ;; Guess away until we find the real file type.
373 (while (assq 'guess (setq defs (cdr (assq nndoc-article-type
374 nndoc-type-alist))))
375 (setq nndoc-article-type (nndoc-guess-type nndoc-article-type)))
376 ;; Set the nndoc variables.
377 (while defs
378 (set (intern (format "nndoc-%s" (caar defs)))
379 (cdr (pop defs))))))
381 (defun nndoc-guess-type (subtype)
382 (let ((alist nndoc-type-alist)
383 results result entry)
384 (while (and (not result)
385 (setq entry (pop alist)))
386 (when (memq subtype (or (cdr (assq 'subtype entry)) '(guess)))
387 (goto-char (point-min))
388 ;; Remove blank lines.
389 (while (eq (following-char) ?\n)
390 (delete-char 1))
391 (when (numberp (setq result (funcall (intern
392 (format "nndoc-%s-type-p"
393 (car entry))))))
394 (push (cons result entry) results)
395 (setq result nil))))
396 (unless (or result results)
397 (error "Document is not of any recognized type"))
398 (if result
399 (car entry)
400 (cadar (last (sort results 'car-less-than-car))))))
403 ;;; Built-in type predicates and functions
406 (defun nndoc-mbox-type-p ()
407 (when (looking-at message-unix-mail-delimiter)
410 (defun nndoc-mbox-article-begin ()
411 (when (re-search-forward (concat "^" message-unix-mail-delimiter) nil t)
412 (goto-char (match-beginning 0))))
414 (defun nndoc-mbox-body-end ()
415 (let ((beg (point))
416 len end)
417 (when
418 (save-excursion
419 (and (re-search-backward
420 (concat "^" message-unix-mail-delimiter) nil t)
421 (setq end (point))
422 (search-forward "\n\n" beg t)
423 (re-search-backward
424 "^Content-Length:[ \t]*\\([0-9]+\\) *$" end t)
425 (setq len (string-to-number (match-string 1)))
426 (search-forward "\n\n" beg t)
427 (unless (= (setq len (+ (point) len)) (point-max))
428 (and (< len (point-max))
429 (goto-char len)
430 (looking-at message-unix-mail-delimiter)))))
431 (goto-char len))))
433 (defun nndoc-mmdf-type-p ()
434 (when (looking-at "\^A\^A\^A\^A$")
437 (defun nndoc-news-type-p ()
438 (when (looking-at "^Path:.*\n")
441 (defun nndoc-rnews-type-p ()
442 (when (looking-at "#! *rnews")
445 (defun nndoc-rnews-body-end ()
446 (and (re-search-backward nndoc-article-begin nil t)
447 (forward-line 1)
448 (goto-char (+ (point) (string-to-number (match-string 1))))))
450 (defun nndoc-babyl-type-p ()
451 (when (re-search-forward "\^_\^L *\n" nil t)
454 (defun nndoc-babyl-body-begin ()
455 (re-search-forward "^\n" nil t)
456 (when (looking-at "\\*\\*\\* EOOH \\*\\*\\*")
457 (let ((next (or (save-excursion
458 (re-search-forward nndoc-article-begin nil t))
459 (point-max))))
460 (unless (re-search-forward "^\n" next t)
461 (goto-char next)
462 (forward-line -1)
463 (insert "\n")
464 (forward-line -1)))))
466 (defun nndoc-babyl-head-begin ()
467 (when (re-search-forward "^[0-9].*\n" nil t)
468 (when (looking-at "\\*\\*\\* EOOH \\*\\*\\*")
469 (forward-line 1))
472 (defun nndoc-forward-type-p ()
473 (when (and (re-search-forward "^-+ \\(Start of \\)?forwarded message.*\n+"
474 nil t)
475 (looking-at "[\r\n]*[a-zA-Z][a-zA-Z0-9-]*:\\|^>?From "))
478 (defun nndoc-rfc934-type-p ()
479 (when (and (re-search-forward "^-+ Start of forwarded.*\n+" nil t)
480 (not (re-search-forward "^Subject:.*digest" nil t))
481 (not (re-search-backward "^From:" nil t 2))
482 (not (re-search-forward "^From:" nil t 2)))
485 (defun nndoc-mailman-type-p ()
486 (when (re-search-forward "^--__--__--\n+" nil t)
489 (defun nndoc-rfc822-forward-type-p ()
490 (save-restriction
491 (message-narrow-to-head)
492 (when (re-search-forward "^Content-Type: *message/rfc822" nil t)
493 t)))
495 (defun nndoc-rfc822-forward-body-end-function ()
496 (goto-char (point-max)))
498 (defun nndoc-rfc822-forward-generate-article (article &optional head)
499 (let ((entry (cdr (assq article nndoc-dissection-alist)))
500 (begin (point))
501 encoding)
502 (with-current-buffer nndoc-current-buffer
503 (save-restriction
504 (message-narrow-to-head)
505 (setq encoding (message-fetch-field "content-transfer-encoding"))))
506 (insert-buffer-substring nndoc-current-buffer (car entry) (nth 3 entry))
507 (when encoding
508 (save-restriction
509 (narrow-to-region begin (point-max))
510 (mm-decode-content-transfer-encoding
511 (intern (downcase (mail-header-strip encoding))))))
512 (when head
513 (goto-char begin)
514 (when (search-forward "\n\n" nil t)
515 (delete-region (1- (point)) (point-max)))))
518 (defun nndoc-rfc822-forward-generate-head (article)
519 (nndoc-rfc822-forward-generate-article article 'head))
521 (defun nndoc-mime-parts-type-p ()
522 (let ((case-fold-search t)
523 (limit (search-forward "\n\n" nil t)))
524 (goto-char (point-min))
525 (when (and limit
526 (re-search-forward
527 (concat "\
528 ^Content-Type:[ \t]*multipart/[a-z]+ *; *\\(\\(\n[ \t]\\)?.*;\\)*"
529 "\\(\n[ \t]\\)?[ \t]*boundary=\"?[^\"\n]*[^\" \t\n]")
530 limit t))
531 t)))
533 (defun nndoc-transform-mime-parts (article)
534 (let* ((entry (cdr (assq article nndoc-dissection-alist)))
535 (headers (nth 5 entry)))
536 (when headers
537 (goto-char (point-min))
538 (insert headers))))
540 (defun nndoc-generate-mime-parts-head (article)
541 (let* ((entry (cdr (assq article nndoc-dissection-alist)))
542 (headers (nth 6 entry)))
543 (save-restriction
544 (narrow-to-region (point) (point))
545 (insert-buffer-substring
546 nndoc-current-buffer (car entry) (nth 1 entry))
547 (goto-char (point-max)))
548 (when headers
549 (insert headers))))
551 (defun nndoc-clari-briefs-type-p ()
552 (when (let ((case-fold-search nil))
553 (re-search-forward "^\t[^a-z]+ ([^a-z]+) --" nil t))
556 (defun nndoc-transform-clari-briefs (article)
557 (goto-char (point-min))
558 (when (looking-at " *\\*\\(.*\\)\n")
559 (replace-match "" t t))
560 (nndoc-generate-clari-briefs-head article))
562 (defun nndoc-generate-clari-briefs-head (article)
563 (let ((entry (cdr (assq article nndoc-dissection-alist)))
564 subject from)
565 (save-excursion
566 (set-buffer nndoc-current-buffer)
567 (save-restriction
568 (narrow-to-region (car entry) (nth 3 entry))
569 (goto-char (point-min))
570 (when (looking-at " *\\*\\(.*\\)$")
571 (setq subject (match-string 1))
572 (when (string-match "[ \t]+$" subject)
573 (setq subject (substring subject 0 (match-beginning 0)))))
574 (when
575 (let ((case-fold-search nil))
576 (re-search-forward
577 "^\t\\([^a-z]+\\(,[^(]+\\)? ([^a-z]+)\\) --" nil t))
578 (setq from (match-string 1)))))
579 (insert "From: " "clari@clari.net (" (or from "unknown") ")"
580 "\nSubject: " (or subject "(no subject)") "\n")))
582 (defun nndoc-exim-bounce-type-p ()
583 (and (re-search-forward "^------ This is a copy of the message, including all the headers. ------" nil t)
586 (defun nndoc-exim-bounce-body-end-function ()
587 (goto-char (point-max)))
590 (defun nndoc-mime-digest-type-p ()
591 (let ((case-fold-search t)
592 boundary-id b-delimiter entry)
593 (when (and
594 (re-search-forward
595 (concat "^Content-Type: *multipart/digest;[ \t\n]*[ \t]"
596 "boundary=\"?\\([^\"\n]*[^\" \t\n]\\)")
597 nil t)
598 (match-beginning 1))
599 (setq boundary-id (match-string 1)
600 b-delimiter (concat "\n--" boundary-id "[ \t]*$"))
601 (setq entry (assq 'mime-digest nndoc-type-alist))
602 (setcdr entry
603 (list
604 (cons 'head-begin "^ ?\n")
605 (cons 'head-end "^ ?$")
606 (cons 'body-begin "^ ?\n")
607 (cons 'article-begin b-delimiter)
608 (cons 'body-end-function 'nndoc-digest-body-end)
609 (cons 'file-end (concat "^--" boundary-id "--[ \t]*$"))))
610 t)))
612 (defun nndoc-standard-digest-type-p ()
613 (when (and (re-search-forward (concat "^" (make-string 70 ?-) "\n\n") nil t)
614 (re-search-forward
615 (concat "\n\n" (make-string 30 ?-) "\n\n") nil t))
618 (defun nndoc-digest-body-end ()
619 (and (re-search-forward nndoc-article-begin nil t)
620 (goto-char (match-beginning 0))))
622 (defun nndoc-slack-digest-type-p ()
625 (defun nndoc-lanl-gov-announce-type-p ()
626 (when (let ((case-fold-search nil))
627 (re-search-forward "^\\\\\\\\\nPaper\\( (\\*cross-listing\\*)\\)?: [a-zA-Z-\\.]+/[0-9]+" nil t))
630 (defun nndoc-transform-lanl-gov-announce (article)
631 (goto-char (point-max))
632 (when (re-search-backward "^\\\\\\\\ +( *\\([^ ]*\\) , *\\([^ ]*\\))" nil t)
633 (replace-match "\n\nGet it at \\1 (\\2)" t nil))
634 (goto-char (point-min))
635 (while (re-search-forward "^\\\\\\\\$" nil t)
636 (replace-match "" t nil))
637 (goto-char (point-min))
638 (when (re-search-forward "^replaced with revised version +\\(.*[^ ]\\) +" nil t)
639 (replace-match "Date: \\1 (revised) " t nil))
640 (goto-char (point-min))
641 (unless (re-search-forward "^From" nil t)
642 (goto-char (point-min))
643 (when (re-search-forward "^Authors?: \\(.*\\)" nil t)
644 (goto-char (point-min))
645 (insert "From: " (match-string 1) "\n"))))
647 (defun nndoc-generate-lanl-gov-head (article)
648 (let ((entry (cdr (assq article nndoc-dissection-alist)))
649 (from "<no address given>")
650 subject date)
651 (save-excursion
652 (set-buffer nndoc-current-buffer)
653 (save-restriction
654 (narrow-to-region (car entry) (nth 1 entry))
655 (goto-char (point-min))
656 (when (looking-at "^Paper.*: \\([a-zA-Z-\\.]+/[0-9]+\\)")
657 (setq subject (concat " (" (match-string 1) ")"))
658 (when (re-search-forward "^From: \\(.*\\)" nil t)
659 (setq from (concat "<"
660 (cadr (funcall gnus-extract-address-components
661 (match-string 1))) ">")))
662 (if (re-search-forward "^Date: +\\([^(]*\\)" nil t)
663 (setq date (match-string 1))
664 (when (re-search-forward "^replaced with revised version +\\([^(]*\\)" nil t)
665 (setq date (match-string 1))))
666 (when (re-search-forward "^Title: \\([^\f]*\\)\nAuthors?: \\(.*\\)"
667 nil t)
668 (setq subject (concat (match-string 1) subject))
669 (setq from (concat (match-string 2) " " from))))))
670 (while (and from (string-match "(\[^)\]*)" from))
671 (setq from (replace-match "" t t from)))
672 (insert "From: " (or from "unknown")
673 "\nSubject: " (or subject "(no subject)") "\n")
674 (if date (insert "Date: " date))))
676 (defun nndoc-nsmail-type-p ()
677 (when (looking-at "From - ")
680 (defun nndoc-outlook-article-begin ()
681 (prog1 (re-search-forward "From:\\|Received:" nil t)
682 (goto-char (match-beginning 0))))
684 (defun nndoc-outlook-type-p ()
685 ;; FIXME: Is JMF the magic of outlook mailbox? -- ShengHuo.
686 (looking-at "JMF"))
688 (defun nndoc-oe-dbx-type-p ()
689 (looking-at (mm-string-as-multibyte "\317\255\022\376")))
691 (defun nndoc-read-little-endian ()
692 (+ (prog1 (char-after) (forward-char 1))
693 (lsh (prog1 (char-after) (forward-char 1)) 8)
694 (lsh (prog1 (char-after) (forward-char 1)) 16)
695 (lsh (prog1 (char-after) (forward-char 1)) 24)))
697 (defun nndoc-oe-dbx-decode-block ()
698 (list
699 (nndoc-read-little-endian) ;; this address
700 (nndoc-read-little-endian) ;; next address offset
701 (nndoc-read-little-endian) ;; blocksize
702 (nndoc-read-little-endian))) ;; next address
704 (defun nndoc-oe-dbx-dissection ()
705 (let ((i 0) blk p tp)
706 (goto-char 60117) ;; 0x0000EAD4+1
707 (setq p (point))
708 (unless (eobp)
709 (setq blk (nndoc-oe-dbx-decode-block)))
710 (while (and blk (> (car blk) 0) (or (zerop (nth 3 blk))
711 (> (nth 3 blk) p)))
712 (push (list (incf i) p nil nil nil 0) nndoc-dissection-alist)
713 (while (and (> (car blk) 0) (> (nth 3 blk) p))
714 (goto-char (1+ (nth 3 blk)))
715 (setq blk (nndoc-oe-dbx-decode-block)))
716 (if (or (<= (car blk) p)
717 (<= (nth 1 blk) 0)
718 (not (zerop (nth 3 blk))))
719 (setq blk nil)
720 (setq tp (+ (car blk) (nth 1 blk) 17))
721 (if (or (<= tp p) (>= tp (point-max)))
722 (setq blk nil)
723 (goto-char tp)
724 (setq p tp
725 blk (nndoc-oe-dbx-decode-block)))))))
727 (defun nndoc-oe-dbx-generate-article (article &optional head)
728 (let ((entry (cdr (assq article nndoc-dissection-alist)))
729 (cur (current-buffer))
730 (begin (point))
731 blk p)
732 (with-current-buffer nndoc-current-buffer
733 (setq p (car entry))
734 (while (> p (point-min))
735 (goto-char p)
736 (setq blk (nndoc-oe-dbx-decode-block))
737 (setq p (point))
738 (with-current-buffer cur
739 (insert-buffer-substring nndoc-current-buffer p (+ p (nth 2 blk))))
740 (setq p (1+ (nth 3 blk)))))
741 (goto-char begin)
742 (while (re-search-forward "\r$" nil t)
743 (delete-backward-char 1))
744 (when head
745 (goto-char begin)
746 (when (search-forward "\n\n" nil t)
747 (setcar (cddddr entry) (count-lines (point) (point-max)))
748 (delete-region (1- (point)) (point-max))))
751 (defun nndoc-oe-dbx-generate-head (article)
752 (nndoc-oe-dbx-generate-article article 'head))
754 (defun nndoc-mail-in-mail-type-p ()
755 (let (found)
756 (save-excursion
757 (catch 'done
758 (while (re-search-forward "\n\n[-A-Za-z0-9]+:" nil t)
759 (setq found 0)
760 (forward-line)
761 (while (looking-at "[ \t]\\|[-A-Za-z0-9]+:")
762 (if (looking-at "[-A-Za-z0-9]+:")
763 (setq found (1+ found)))
764 (forward-line))
765 (if (and (> found 0) (looking-at "\n"))
766 (throw 'done 9999)))
767 nil))))
769 (defun nndoc-mail-in-mail-article-begin ()
770 (let (point found)
771 (if (catch 'done
772 (while (re-search-forward "\n\n\\([-A-Za-z0-9]+:\\)" nil t)
773 (setq found 0)
774 (setq point (match-beginning 1))
775 (forward-line)
776 (while (looking-at "[ \t]\\|[-A-Za-z0-9]+:")
777 (if (looking-at "[-A-Za-z0-9]+:")
778 (setq found (1+ found)))
779 (forward-line))
780 (if (and (> found 0) (looking-at "\n"))
781 (throw 'done t)))
782 nil)
783 (goto-char point))))
785 (deffoo nndoc-request-accept-article (group &optional server last)
786 nil)
789 ;;; Functions for dissecting the documents
792 (defun nndoc-search (regexp)
793 (prog1
794 (re-search-forward regexp nil t)
795 (beginning-of-line)))
797 (defun nndoc-dissect-buffer ()
798 "Go through the document and partition it into heads/bodies/articles."
799 (let ((i 0)
800 (first t)
801 art-begin head-begin head-end body-begin body-end)
802 (setq nndoc-dissection-alist nil)
803 (save-excursion
804 (set-buffer nndoc-current-buffer)
805 (goto-char (point-min))
806 ;; Remove blank lines.
807 (while (eq (following-char) ?\n)
808 (delete-char 1))
809 (if nndoc-dissection-function
810 (funcall nndoc-dissection-function)
811 ;; Find the beginning of the file.
812 (when nndoc-file-begin
813 (nndoc-search nndoc-file-begin))
814 ;; Go through the file.
815 (while (if (and first nndoc-first-article)
816 (nndoc-search nndoc-first-article)
817 (if art-begin
818 (goto-char art-begin)
819 (nndoc-article-begin)))
820 (setq first nil
821 art-begin nil)
822 (cond (nndoc-head-begin-function
823 (funcall nndoc-head-begin-function))
824 (nndoc-head-begin
825 (nndoc-search nndoc-head-begin)))
826 (if (or (eobp)
827 (and nndoc-file-end
828 (looking-at nndoc-file-end)))
829 (goto-char (point-max))
830 (setq head-begin (point))
831 (nndoc-search (or nndoc-head-end "^$"))
832 (setq head-end (point))
833 (if nndoc-body-begin-function
834 (funcall nndoc-body-begin-function)
835 (nndoc-search (or nndoc-body-begin "^\n")))
836 (setq body-begin (point))
837 (or (and nndoc-body-end-function
838 (funcall nndoc-body-end-function))
839 (and nndoc-body-end
840 (nndoc-search nndoc-body-end))
841 (and (nndoc-article-begin)
842 (setq art-begin (point)))
843 (progn
844 (goto-char (point-max))
845 (when nndoc-file-end
846 (and (re-search-backward nndoc-file-end nil t)
847 (beginning-of-line)))))
848 (setq body-end (point))
849 (push (list (incf i) head-begin head-end body-begin body-end
850 (count-lines body-begin body-end))
851 nndoc-dissection-alist)))))))
853 (defun nndoc-article-begin ()
854 (if nndoc-article-begin-function
855 (funcall nndoc-article-begin-function)
856 (ignore-errors
857 (nndoc-search nndoc-article-begin))))
859 (defun nndoc-unquote-dashes ()
860 "Unquote quoted non-separators in digests."
861 (while (re-search-forward "^- -"nil t)
862 (replace-match "-" t t)))
864 ;; Against compiler warnings.
865 (defvar nndoc-mime-split-ordinal)
867 (defun nndoc-dissect-mime-parts ()
868 "Go through a MIME composite article and partition it into sub-articles.
869 When a MIME entity contains sub-entities, dissection produces one article for
870 the header of this entity, and one article per sub-entity."
871 (setq nndoc-dissection-alist nil
872 nndoc-mime-split-ordinal 0)
873 (save-excursion
874 (set-buffer nndoc-current-buffer)
875 (nndoc-dissect-mime-parts-sub (point-min) (point-max) nil nil nil)))
877 (defun nndoc-dissect-mime-parts-sub (head-begin body-end article-insert
878 position parent)
879 "Dissect an entity, within a composite MIME message.
880 The complete message or MIME entity extends from HEAD-BEGIN to BODY-END.
881 ARTICLE-INSERT should be added at beginning for generating a full article.
882 The string POSITION holds a dotted decimal representation of the article
883 position in the hierarchical structure, it is nil for the outer entity.
884 PARENT is the message-ID of the parent summary line, or nil for none."
885 (let ((case-fold-search t)
886 (message-id (nnmail-message-id))
887 head-end body-begin summary-insert message-rfc822 multipart-any
888 subject content-type type subtype boundary-regexp)
889 ;; Gracefully handle a missing body.
890 (goto-char head-begin)
891 (if (or (and (eq (char-after) ?\n) (or (forward-char 1) t))
892 (search-forward "\n\n" body-end t))
893 (setq head-end (1- (point))
894 body-begin (point))
895 (setq head-end body-end
896 body-begin body-end))
897 (narrow-to-region head-begin head-end)
898 ;; Save MIME attributes.
899 (goto-char head-begin)
900 (setq content-type (message-fetch-field "Content-Type"))
901 (when content-type
902 (when (string-match
903 "^ *\\([^ \t\n/;]+\\)/\\([^ \t\n/;]+\\)" content-type)
904 (setq type (downcase (match-string 1 content-type))
905 subtype (downcase (match-string 2 content-type))
906 message-rfc822 (and (string= type "message")
907 (string= subtype "rfc822"))
908 multipart-any (string= type "multipart")))
909 (when (string-match ";[ \t\n]*name=\\([^ \t\n;]+\\)" content-type)
910 (setq subject (match-string 1 content-type)))
911 (when (string-match "boundary=\"?\\([^\"\n]*[^\" \t\n]\\)" content-type)
912 (setq boundary-regexp (concat "^--"
913 (regexp-quote
914 (match-string 1 content-type))
915 "\\(--\\)?[ \t]*\n"))))
916 (unless subject
917 (when (or multipart-any (not article-insert))
918 (setq subject (message-fetch-field "Subject"))))
919 (unless type
920 (setq type "text"
921 subtype "plain"))
922 ;; Prepare the article and summary inserts.
923 (unless article-insert
924 (setq article-insert (buffer-string)
925 head-end head-begin))
926 ;; Fix MIME-Version
927 (unless (string-match "MIME-Version:" article-insert)
928 (setq article-insert
929 (concat article-insert "MIME-Version: 1.0\n")))
930 (setq summary-insert article-insert)
931 ;; - summary Subject.
932 (setq summary-insert
933 (let ((line (concat "Subject: <" position
934 (and position multipart-any ".")
935 (and multipart-any "*")
936 (and (or position multipart-any) " ")
937 (cond ((string= subtype "plain") type)
938 ((string= subtype "basic") type)
939 (t subtype))
941 (and subject " ")
942 subject
943 "\n")))
944 (if (string-match "Subject:.*\n\\([ \t].*\n\\)*" summary-insert)
945 (replace-match line t t summary-insert)
946 (concat summary-insert line))))
947 ;; - summary Message-ID.
948 (setq summary-insert
949 (let ((line (concat "Message-ID: " message-id "\n")))
950 (if (string-match "Message-ID:.*\n\\([ \t].*\n\\)*" summary-insert)
951 (replace-match line t t summary-insert)
952 (concat summary-insert line))))
953 ;; - summary References.
954 (when parent
955 (setq summary-insert
956 (let ((line (concat "References: " parent "\n")))
957 (if (string-match "References:.*\n\\([ \t].*\n\\)*"
958 summary-insert)
959 (replace-match line t t summary-insert)
960 (concat summary-insert line)))))
961 ;; Generate dissection information for this entity.
962 (push (list (incf nndoc-mime-split-ordinal)
963 head-begin head-end body-begin body-end
964 (count-lines body-begin body-end)
965 article-insert summary-insert)
966 nndoc-dissection-alist)
967 ;; Recurse for all sub-entities, if any.
968 (widen)
969 (cond
970 (message-rfc822
971 (save-excursion
972 (nndoc-dissect-mime-parts-sub body-begin body-end nil
973 position message-id)))
974 ((and multipart-any boundary-regexp)
975 (let ((part-counter 0)
976 part-begin part-end eof-flag)
977 (while (string-match "\
978 ^\\(Lines\\|Content-\\(Type\\|Transfer-Encoding\\|Disposition\\)\\):.*\n\\([ \t].*\n\\)*"
979 article-insert)
980 (setq article-insert (replace-match "" t t article-insert)))
981 (let ((case-fold-search nil))
982 (goto-char body-begin)
983 (setq eof-flag (not (re-search-forward boundary-regexp body-end t)))
984 (while (not eof-flag)
985 (setq part-begin (point))
986 (cond ((re-search-forward boundary-regexp body-end t)
987 (or (not (match-string 1))
988 (string= (match-string 1) "")
989 (setq eof-flag t))
990 (forward-line -1)
991 (setq part-end (point))
992 (forward-line 1))
993 (t (setq part-end body-end
994 eof-flag t)))
995 (save-excursion
996 (nndoc-dissect-mime-parts-sub
997 part-begin part-end article-insert
998 (concat position
999 (and position ".")
1000 (format "%d" (incf part-counter)))
1001 message-id)))))))))
1003 ;;;###autoload
1004 (defun nndoc-add-type (definition &optional position)
1005 "Add document DEFINITION to the list of nndoc document definitions.
1006 If POSITION is nil or `last', the definition will be added
1007 as the last checked definition, if t or `first', add as the
1008 first definition, and if any other symbol, add after that
1009 symbol in the alist."
1010 ;; First remove any old instances.
1011 (gnus-pull (car definition) nndoc-type-alist)
1012 ;; Then enter the new definition in the proper place.
1013 (cond
1014 ((or (null position) (eq position 'last))
1015 (setq nndoc-type-alist (nconc nndoc-type-alist (list definition))))
1016 ((or (eq position t) (eq position 'first))
1017 (push definition nndoc-type-alist))
1019 (let ((list (memq (assq position nndoc-type-alist)
1020 nndoc-type-alist)))
1021 (unless list
1022 (error "No such position: %s" position))
1023 (setcdr list (cons definition (cdr list)))))))
1025 (provide 'nndoc)
1027 ;;; arch-tag: f5c2970e-0387-47ac-a0b3-6cc317dffabe
1028 ;;; nndoc.el ends here