*** empty log message ***
[gnus] / lisp / nndoc.el
1 ;; Copyright (C) 1995,96 Free Software Foundation, Inc.
2
3 ;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
4 ;;      Masanobu UMEDA <umerin@flab.flab.fujitsu.junet>
5 ;; Keywords: news
6
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 (require 'nnheader)
29 (require 'message)
30 (require 'nnmail)
31 (require 'nnoo)
32 (eval-when-compile (require 'cl))
33
34 (nnoo-declare nndoc)
35
36 (defvoo nndoc-article-type 'guess
37   "*Type of the file.
38 One of `mbox', `babyl', `digest', `news', `rnews', `mmdf', `forward',
39 `mime-digest', `standard-digest', `slack-digest', `clari-briefs' or
40 `guess'.")
41
42 (defvoo nndoc-post-type 'mail
43   "*Whether the nndoc group is `mail' or `post'.")
44
45 (defvar nndoc-type-alist 
46   `((mmdf 
47      (article-begin .  "^\^A\^A\^A\^A\n")
48      (body-end .  "^\^A\^A\^A\^A\n"))
49     (news
50      (article-begin . "^Path:"))
51     (rnews
52      (article-begin . "^#! *rnews +\\([0-9]+\\) *\n")
53      (body-end-function . nndoc-rnews-body-end))
54     (mbox 
55      (article-begin-function . nndoc-mbox-article-begin)
56      (body-end-function . nndoc-mbox-body-end))
57     (babyl 
58      (article-begin . "\^_\^L *\n")
59      (body-end . "\^_")
60      (body-begin-function . nndoc-babyl-body-begin)
61      (head-begin-function . nndoc-babyl-head-begin))
62     (forward
63      (article-begin . "^-+ Start of forwarded message -+\n+")
64      (body-end . "^-+ End of forwarded message -+$")
65      (prepare-body-function . nndoc-unquote-dashes))
66     (clari-briefs
67      (article-begin . "^ \\*")
68      (body-end . "^\t------*[ \t]^*\n^ \\*")
69      (body-begin . "^\t")
70      (head-end . "^\t")
71      (generate-head-function . nndoc-generate-clari-briefs-head)
72      (article-transform-function . nndoc-transform-clari-briefs))
73     (mime-digest
74      (article-begin . "")
75      (head-end . "^ ?$")
76      (body-end . "")
77      (file-end . "")
78      (subtype digest guess))
79     (standard-digest
80      (first-article . ,(concat "^" (make-string 70 ?-) "\n\n+"))
81      (article-begin . ,(concat "^\n" (make-string 30 ?-) "\n\n+"))
82      (prepare-body-function . nndoc-unquote-dashes)
83      (body-end-function . nndoc-digest-body-end)
84      (head-end . "^ ?$")
85      (body-begin . "^ ?\n")
86      (file-end . "^End of .*digest.*[0-9].*\n\\*\\*\\|^End of.*Digest *$")
87      (subtype digest guess))
88     (slack-digest
89      (article-begin . "^------------------------------*[\n \t]+")
90      (head-end . "^ ?$")
91      (body-end-function . nndoc-digest-body-end)
92      (body-begin . "^ ?$")
93      (file-end . "^End of")
94      (prepare-body-function . nndoc-unquote-dashes)
95      (subtype digest guess))
96     (lanl-gov-announce
97       (article-begin . "^\\\\\\\\\n")
98       (head-begin . "^Paper.*:")
99       (head-end   . "\\(^\\\\\\\\.*\n\\|-----------------\\)")
100       (body-begin . "")  
101       (body-end   . "-------------------------------------------------")     
102       (file-end   . "^Title: Recent Seminal")
103       (generate-head-function . nndoc-generate-lanl-gov-head)
104       (article-transform-function . nndoc-transform-lanl-gov-announce)
105       (subtype preprints guess))
106     (guess 
107      (guess . t)
108      (subtype nil))
109     (digest
110      (guess . t)
111      (subtype nil))
112     (preprints
113      (guess . t)
114      (subtype nil))
115     ))
116
117 \f
118
119 (defvoo nndoc-file-begin nil)
120 (defvoo nndoc-first-article nil)
121 (defvoo nndoc-article-end nil)
122 (defvoo nndoc-article-begin nil)
123 (defvoo nndoc-head-begin nil)
124 (defvoo nndoc-head-end nil)
125 (defvoo nndoc-file-end nil)
126 (defvoo nndoc-body-begin nil)
127 (defvoo nndoc-body-end-function nil)
128 (defvoo nndoc-body-begin-function nil)
129 (defvoo nndoc-head-begin-function nil)
130 (defvoo nndoc-body-end nil)
131 (defvoo nndoc-dissection-alist nil)
132 (defvoo nndoc-prepare-body-function nil)
133 (defvoo nndoc-generate-head-function nil)
134 (defvoo nndoc-article-transform-function nil)
135
136 (defvoo nndoc-status-string "")
137 (defvoo nndoc-group-alist nil)
138 (defvoo nndoc-current-buffer nil
139   "Current nndoc news buffer.")
140 (defvoo nndoc-address nil)
141
142 (defconst nndoc-version "nndoc 1.0"
143   "nndoc version.")
144
145 \f
146
147 ;;; Interface functions
148
149 (nnoo-define-basics nndoc)
150
151 (deffoo nndoc-retrieve-headers (articles &optional newsgroup server fetch-old)
152   (when (nndoc-possibly-change-buffer newsgroup server)
153     (save-excursion
154       (set-buffer nntp-server-buffer)
155       (erase-buffer)
156       (let (article entry)
157         (if (stringp (car articles))
158             'headers
159           (while articles
160             (when (setq entry (cdr (assq (setq article (pop articles))
161                                          nndoc-dissection-alist)))
162               (insert (format "221 %d Article retrieved.\n" article))
163               (if nndoc-generate-head-function
164                   (funcall nndoc-generate-head-function article)
165                 (insert-buffer-substring
166                  nndoc-current-buffer (car entry) (nth 1 entry)))
167               (goto-char (point-max))
168               (or (= (char-after (1- (point))) ?\n) (insert "\n"))
169               (insert (format "Lines: %d\n" (nth 4 entry)))
170               (insert ".\n")))
171
172           (nnheader-fold-continuation-lines)
173           'headers)))))
174
175 (deffoo nndoc-request-article (article &optional newsgroup server buffer)
176   (nndoc-possibly-change-buffer newsgroup server)
177   (save-excursion
178     (let ((buffer (or buffer nntp-server-buffer))
179           (entry (cdr (assq article nndoc-dissection-alist)))
180           beg)
181       (set-buffer buffer)
182       (erase-buffer)
183       (if (stringp article)
184           nil
185         (insert-buffer-substring 
186          nndoc-current-buffer (car entry) (nth 1 entry))
187         (insert "\n")
188         (setq beg (point))
189         (insert-buffer-substring 
190          nndoc-current-buffer (nth 2 entry) (nth 3 entry))
191         (goto-char beg)
192         (when nndoc-prepare-body-function
193           (funcall nndoc-prepare-body-function))
194         (when nndoc-article-transform-function
195           (funcall nndoc-article-transform-function article))
196         t))))
197
198 (deffoo nndoc-request-group (group &optional server dont-check)
199   "Select news GROUP."
200   (let (number)
201     (cond 
202      ((not (nndoc-possibly-change-buffer group server))
203       (nnheader-report 'nndoc "No such file or buffer: %s"
204                        nndoc-address))
205      (dont-check
206       (nnheader-report 'nndoc "Selected group %s" group)
207       t)
208      ((zerop (setq number (length nndoc-dissection-alist)))
209       (nndoc-close-group group)
210       (nnheader-report 'nndoc "No articles in group %s" group))
211      (t
212       (nnheader-insert "211 %d %d %d %s\n" number 1 number group)))))
213
214 (deffoo nndoc-request-type (group &optional article)
215   (cond ((not article) 'unknown)
216         (nndoc-post-type nndoc-post-type)
217         (t 'unknown)))
218
219 (deffoo nndoc-close-group (group &optional server)
220   (nndoc-possibly-change-buffer group server)
221   (and nndoc-current-buffer
222        (buffer-name nndoc-current-buffer)
223        (kill-buffer nndoc-current-buffer))
224   (setq nndoc-group-alist (delq (assoc group nndoc-group-alist)
225                                 nndoc-group-alist))
226   (setq nndoc-current-buffer nil)
227   (nnoo-close-server 'nndoc server)
228   (setq nndoc-dissection-alist nil)
229   t)
230
231 (deffoo nndoc-request-list (&optional server)
232   nil)
233
234 (deffoo nndoc-request-newgroups (date &optional server)
235   nil)
236
237 (deffoo nndoc-request-list-newsgroups (&optional server)
238   nil)
239
240 \f
241 ;;; Internal functions.
242
243 (defun nndoc-possibly-change-buffer (group source)
244   (let (buf)
245     (cond 
246      ;; The current buffer is this group's buffer.
247      ((and nndoc-current-buffer
248            (buffer-name nndoc-current-buffer)
249            (eq nndoc-current-buffer 
250                (setq buf (cdr (assoc group nndoc-group-alist))))))
251      ;; We change buffers by taking an old from the group alist.
252      ;; `source' is either a string (a file name) or a buffer object. 
253      (buf
254       (setq nndoc-current-buffer buf))
255      ;; It's a totally new group.    
256      ((or (and (bufferp nndoc-address)
257                (buffer-name nndoc-address))
258           (and (stringp nndoc-address)
259                (file-exists-p nndoc-address)
260                (not (file-directory-p nndoc-address))))
261       (push (cons group (setq nndoc-current-buffer 
262                               (get-buffer-create 
263                                (concat " *nndoc " group "*"))))
264             nndoc-group-alist)
265       (setq nndoc-dissection-alist nil)
266       (save-excursion
267         (set-buffer nndoc-current-buffer)
268         (buffer-disable-undo (current-buffer))
269         (erase-buffer)
270         (if (stringp nndoc-address)
271             (insert-file-contents nndoc-address)
272           (insert-buffer-substring nndoc-address)))))
273     ;; Initialize the nndoc structures according to this new document.
274     (when (and nndoc-current-buffer
275                (not nndoc-dissection-alist))
276       (save-excursion
277         (set-buffer nndoc-current-buffer)
278         (nndoc-set-delims)
279         (nndoc-dissect-buffer)))
280     (unless nndoc-current-buffer
281       (nndoc-close-server))
282     ;; Return whether we managed to select a file.
283     nndoc-current-buffer))
284
285 ;;;
286 ;;; Deciding what document type we have
287 ;;;
288
289 (defun nndoc-set-delims ()
290   "Set the nndoc delimiter variables according to the type of the document."
291   (let ((vars '(nndoc-file-begin 
292                 nndoc-first-article 
293                 nndoc-article-end nndoc-head-begin nndoc-head-end
294                 nndoc-file-end nndoc-article-begin
295                 nndoc-body-begin nndoc-body-end-function nndoc-body-end
296                 nndoc-prepare-body-function nndoc-article-transform-function
297                 nndoc-generate-head-function nndoc-body-begin-function
298                 nndoc-head-begin-function)))
299     (while vars
300       (set (pop vars) nil)))
301   (let (defs)
302     ;; Guess away until we find the real file type.
303     (while (assq 'guess (setq defs (cdr (assq nndoc-article-type 
304                                               nndoc-type-alist))))
305       (setq nndoc-article-type (nndoc-guess-type nndoc-article-type)))
306     ;; Set the nndoc variables.
307     (while defs
308       (set (intern (format "nndoc-%s" (caar defs)))
309            (cdr (pop defs))))))
310
311 (defun nndoc-guess-type (subtype)
312   (let ((alist nndoc-type-alist)
313         results result entry)
314     (while (and (not result)
315                 (setq entry (pop alist)))
316       (when (memq subtype (or (cdr (assq 'subtype entry)) '(guess)))
317         (goto-char (point-min))
318         (when (numberp (setq result (funcall (intern
319                                               (format "nndoc-%s-type-p" 
320                                                       (car entry))))))
321           (push (cons result entry) results)
322           (setq result nil))))
323     (unless (or result results)
324       (error "Document is not of any recognized type"))
325     (if result
326         (car entry)
327       (cadar (sort results (lambda (r1 r2) (< (car r1) (car r2))))))))
328
329 ;;; 
330 ;;; Built-in type predicates and functions
331 ;;;
332
333 (defun nndoc-mbox-type-p ()
334   (when (looking-at message-unix-mail-delimiter)
335     t))
336
337 (defun nndoc-mbox-article-begin ()
338   (when (re-search-forward (concat "^" message-unix-mail-delimiter))
339     (goto-char (match-beginning 0))))
340
341 (defun nndoc-mbox-body-end ()
342   (let ((beg (point))
343         len end)
344     (when
345         (save-excursion
346           (and (re-search-backward nndoc-article-begin nil t)
347                (setq end (point))
348                (search-forward "\n\n" beg t)
349                (re-search-backward
350                 "^Content-Length:[ \t]*\\([0-9]+\\) *$" end t)
351                (setq len (string-to-int (match-string 1)))
352                (search-forward "\n\n" beg t)
353                (or (= (setq len (+ (point) len)) (point-max))
354                    (and (< len (point-max))
355                         (goto-char len)
356                         (looking-at nndoc-article-begin)))))
357       (goto-char len))))
358
359 (defun nndoc-mmdf-type-p ()
360   (when (looking-at "\^A\^A\^A\^A$")
361     t))
362
363 (defun nndoc-news-type-p ()
364   (when (looking-at "^Path:.*\n")
365     t))
366
367 (defun nndoc-rnews-type-p ()
368   (when (looking-at "#! *rnews")
369     t))
370
371 (defun nndoc-rnews-body-end ()
372   (and (re-search-backward nndoc-article-begin nil t)
373        (forward-line 1)
374        (goto-char (+ (point) (string-to-int (match-string 1))))))
375
376 (defun nndoc-babyl-type-p ()
377   (when (re-search-forward "\^_\^L *\n" nil t)
378     t))
379
380 (defun nndoc-babyl-body-begin ()
381   (re-search-forward "^\n" nil t)
382   (when (looking-at "\*\*\* EOOH \*\*\*")
383     (let ((next (or (save-excursion
384                       (re-search-forward nndoc-article-begin nil t))
385                     (point-max))))
386       (unless (re-search-forward "^\n" next t)
387         (goto-char next)
388         (forward-line -1)
389         (insert "\n")
390         (forward-line -1)))))
391
392 (defun nndoc-babyl-head-begin ()
393   (when (re-search-forward "^[0-9].*\n" nil t)
394     (when (looking-at "\*\*\* EOOH \*\*\*")
395       (forward-line 1))
396     t))
397
398 (defun nndoc-forward-type-p ()
399   (when (and (re-search-forward "^-+ Start of forwarded message -+\n+" nil t)
400              (not (re-search-forward "^Subject:.*digest" nil t)))
401     t))
402
403 (defun nndoc-clari-briefs-type-p ()
404   (when (let ((case-fold-search nil))
405           (re-search-forward "^\t[^a-z]+ ([^a-z]+) --" nil t))
406     t))
407
408 (defun nndoc-transform-clari-briefs (article)
409   (goto-char (point-min))
410   (when (looking-at " *\\*\\(.*\\)\n")
411     (replace-match "" t t))
412   (nndoc-generate-clari-briefs-head article))
413
414 (defun nndoc-generate-clari-briefs-head (article)
415   (let ((entry (cdr (assq article nndoc-dissection-alist)))
416         subject from)
417     (save-excursion
418       (set-buffer nndoc-current-buffer)
419       (save-restriction
420         (narrow-to-region (car entry) (nth 3 entry))
421         (goto-char (point-min))
422         (when (looking-at " *\\*\\(.*\\)$")
423           (setq subject (match-string 1))
424           (when (string-match "[ \t]+$" subject)
425             (setq subject (substring subject 0 (match-beginning 0)))))
426         (when
427             (let ((case-fold-search nil))
428               (re-search-forward
429                "^\t\\([^a-z]+\\(,[^(]+\\)? ([^a-z]+)\\) --" nil t))
430           (setq from (match-string 1)))))
431     (insert "From: " "clari@clari.net (" (or from "unknown") ")"
432             "\nSubject: " (or subject "(no subject)") "\n")))
433
434 (defun nndoc-mime-digest-type-p ()
435   (let ((case-fold-search t)
436         boundary-id b-delimiter entry)
437     (when (and
438            (re-search-forward
439             (concat "^Content-Type: *multipart/digest;[ \t\n]*[ \t]"
440                     "boundary=\"\\([^\"\n]*[^\" \t\n]\\)\"")
441             nil t)
442            (match-beginning 1))
443       (setq boundary-id (match-string 1)
444             b-delimiter (concat "\n--" boundary-id "[\n \t]+"))
445       (setq entry (assq 'mime-digest nndoc-type-alist))
446       (setcdr entry
447               (list
448                (cons 'head-end "^ ?$")
449                (cons 'body-begin "^ ?\n")
450                (cons 'article-begin b-delimiter)
451                (cons 'body-end-function 'nndoc-digest-body-end)
452                (cons 'file-end (concat "\n--" boundary-id "--[ \t]*$"))))
453       t)))
454
455 (defun nndoc-standard-digest-type-p ()
456   (when (and (re-search-forward (concat "^" (make-string 70 ?-) "\n\n") nil t)
457              (re-search-forward 
458               (concat "\n\n" (make-string 30 ?-) "\n\n") nil t))
459     t))
460
461 (defun nndoc-digest-body-end ()
462   (and (re-search-forward nndoc-article-begin nil t)
463        (goto-char (match-beginning 0))))
464
465 (defun nndoc-slack-digest-type-p ()
466   0)
467
468 (defun nndoc-lanl-gov-announce-type-p ()
469   (when (let ((case-fold-search nil))
470           (re-search-forward "^\\\\\\\\\nPaper: [a-z-]+/[0-9]+" nil t))
471     t))
472
473 (defun nndoc-transform-lanl-gov-announce (article)
474   (goto-char (point-max))
475   (when (re-search-backward "^\\\\\\\\ +(\\([^ ]*\\) , *\\([^ ]*\\))" nil t)
476     (replace-match "\n\nGet it at \\1 (\\2)" t nil))
477   ;;  (when (re-search-backward "^\\\\\\\\$" nil t)
478   ;;    (replace-match "" t t))
479   )
480  
481 (defun nndoc-generate-lanl-gov-head (article)
482   (let ((entry (cdr (assq article nndoc-dissection-alist)))
483         (e-mail "no address given")
484         subject from)
485     (save-excursion
486       (set-buffer nndoc-current-buffer)
487       (save-restriction
488         (narrow-to-region (car entry) (nth 1 entry))
489         (goto-char (point-min))
490         (when (looking-at "^Paper.*: \\([a-z-]+/[0-9]+\\)")
491           (setq subject (concat " (" (match-string 1) ")"))
492           (when (re-search-forward "^From: \\([^ ]+\\)" nil t)
493             (setq e-mail (match-string 1)))
494           (when (re-search-forward "^Title: \\([^\f]*\\)\nAuthors?: \\(.*\\)"
495                                    nil t)
496             (setq subject (concat (match-string 1) subject))
497             (setq from (concat (match-string 2) " <" e-mail ">"))))
498         ))
499     (while (and from (string-match "(\[^)\]*)" from))
500       (setq from (replace-match "" t t from)))
501     (insert "From: "  (or from "unknown")
502             "\nSubject: " (or subject "(no subject)") "\n")))
503  
504
505
506 ;;;
507 ;;; Functions for dissecting the documents
508 ;;;
509
510 (defun nndoc-search (regexp)
511   (prog1
512       (re-search-forward regexp nil t)
513     (beginning-of-line)))
514
515 (defun nndoc-dissect-buffer ()
516   "Go through the document and partition it into heads/bodies/articles."
517   (let ((i 0)
518         (first t)
519         head-begin head-end body-begin body-end)
520     (setq nndoc-dissection-alist nil)
521     (save-excursion
522       (set-buffer nndoc-current-buffer)
523       (goto-char (point-min))
524       ;; Find the beginning of the file.
525       (when nndoc-file-begin
526         (nndoc-search nndoc-file-begin))
527       ;; Go through the file.
528       (while (if (and first nndoc-first-article)
529                  (nndoc-search nndoc-first-article)
530                (condition-case ()
531                    (nndoc-search nndoc-article-begin)
532                  (error nil)))
533         (setq first nil)
534         (cond (nndoc-head-begin-function
535                (funcall nndoc-head-begin-function))
536               (nndoc-head-begin 
537                (nndoc-search nndoc-head-begin)))
538         (if (or (>= (point) (point-max))
539                 (and nndoc-file-end
540                      (looking-at nndoc-file-end)))
541             (goto-char (point-max))
542           (setq head-begin (point))
543           (nndoc-search (or nndoc-head-end "^$"))
544           (setq head-end (point))
545           (if nndoc-body-begin-function
546               (funcall nndoc-body-begin-function)
547             (nndoc-search (or nndoc-body-begin "^\n")))
548           (setq body-begin (point))
549           (or (and nndoc-body-end-function
550                    (funcall nndoc-body-end-function))
551               (and nndoc-body-end
552                    (nndoc-search nndoc-body-end))
553               (condition-case ()
554                   (nndoc-search nndoc-article-begin)
555                 (error nil))
556               (progn
557                 (goto-char (point-max))
558                 (when nndoc-file-end
559                   (and (re-search-backward nndoc-file-end nil t)
560                        (beginning-of-line)))))
561           (setq body-end (point))
562           (push (list (incf i) head-begin head-end body-begin body-end
563                       (count-lines body-begin body-end))
564                 nndoc-dissection-alist))))))
565
566 (defun nndoc-unquote-dashes ()
567   "Unquote quoted non-separators in digests."
568   (while (re-search-forward "^- -"nil t)
569     (replace-match "-" t t)))
570
571 ;;;###autoload
572 (defun nndoc-add-type (definition &optional position)
573   "Add document DEFINITION to the list of nndoc document definitions.
574 If POSITION is nil or `last', the definition will be added
575 as the last checked definition, if t or `first', add as the
576 first definition, and if any other symbol, add after that
577 symbol in the alist."
578   ;; First remove any old instances.
579   (setq nndoc-type-alist
580         (delq (assq (car definition) nndoc-type-alist)
581               nndoc-type-alist))
582   ;; Then enter the new definition in the proper place.
583   (cond
584    ((or (null position) (eq position 'last))
585     (setq nndoc-type-alist (nconc nndoc-type-alist (list definition))))
586    ((or (eq position t) (eq position 'first))
587     (push definition nndoc-type-alist))
588    (t
589     (let ((list (memq (assq position nndoc-type-alist)
590                       nndoc-type-alist)))
591       (unless list
592         (error "No such position: %s" position))
593       (setcdr list (cons definition (cdr list)))))))
594
595 (provide 'nndoc)
596
597 ;;; nndoc.el ends here