See ChangeLog for the log entries
[gnus] / lisp / nnweb.el
1 ;;; nnweb.el --- retrieving articles via web search engines
2 ;; Copyright (C) 1996,97,98,99 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
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 ;; Note: You need to have `url' and `w3' installed for this
27 ;; backend to work.
28
29 ;;; Code:
30
31 (eval-when-compile (require 'cl))
32
33 (require 'nnoo)
34 (require 'message)
35 (require 'gnus-util)
36 (require 'gnus)
37 (require 'nnmail)
38 (require 'mm-util)
39 (eval-when-compile
40   (ignore-errors
41     (require 'w3)
42     (require 'url)
43     (require 'w3-forms)))
44 ;; Report failure to find w3 at load time if appropriate.
45 (eval '(progn
46          (require 'w3)
47          (require 'url)
48          (require 'w3-forms)))
49
50 (nnoo-declare nnweb)
51
52 (defvoo nnweb-directory (nnheader-concat gnus-directory "nnweb/")
53   "Where nnweb will save its files.")
54
55 (defvoo nnweb-type 'dejanews
56   "What search engine type is being used.
57 Valid types include `dejanews', `dejanewsold', `reference',
58 and `altavista'.")
59
60 (defvar nnweb-type-definition
61   '((dejanews
62      (article . ignore)
63      (id . "http://search.dejanews.com/msgid.xp?MID=%s&fmt=text")
64      (map . nnweb-dejanews-create-mapping)
65      (search . nnweb-dejanews-search)
66      (address . "http://www.deja.com/=dnc/qs.xp")
67      (identifier . nnweb-dejanews-identity))
68     (dejanewsold
69      (article . ignore)
70      (map . nnweb-dejanews-create-mapping)
71      (search . nnweb-dejanewsold-search)
72      (address . "http://www.deja.com/dnquery.xp")
73      (identifier . nnweb-dejanews-identity))
74     (reference
75      (article . nnweb-reference-wash-article)
76      (map . nnweb-reference-create-mapping)
77      (search . nnweb-reference-search)
78      (address . "http://www.reference.com/cgi-bin/pn/go")
79      (identifier . identity))
80     (altavista
81      (article . nnweb-altavista-wash-article)
82      (map . nnweb-altavista-create-mapping)
83      (search . nnweb-altavista-search)
84      (address . "http://www.altavista.digital.com/cgi-bin/query")
85      (id . "/cgi-bin/news?id@%s")
86      (identifier . identity)))
87   "Type-definition alist.")
88
89 (defvoo nnweb-search nil
90   "Search string to feed to DejaNews.")
91
92 (defvoo nnweb-max-hits 999
93   "Maximum number of hits to display.")
94
95 (defvoo nnweb-ephemeral-p nil
96   "Whether this nnweb server is ephemeral.")
97
98 ;;; Internal variables
99
100 (defvoo nnweb-articles nil)
101 (defvoo nnweb-buffer nil)
102 (defvoo nnweb-group-alist nil)
103 (defvoo nnweb-group nil)
104 (defvoo nnweb-hashtb nil)
105
106 ;;; Interface functions
107
108 (nnoo-define-basics nnweb)
109
110 (deffoo nnweb-retrieve-headers (articles &optional group server fetch-old)
111   (nnweb-possibly-change-server group server)
112   (save-excursion
113     (set-buffer nntp-server-buffer)
114     (erase-buffer)
115     (let (article header)
116       (while (setq article (pop articles))
117         (when (setq header (cadr (assq article nnweb-articles)))
118           (nnheader-insert-nov header)))
119       'nov)))
120
121 (deffoo nnweb-request-scan (&optional group server)
122   (nnweb-possibly-change-server group server)
123   (setq nnweb-hashtb (gnus-make-hashtable 4095))
124   (funcall (nnweb-definition 'map))
125   (unless nnweb-ephemeral-p
126     (nnweb-write-active)
127     (nnweb-write-overview group)))
128
129 (deffoo nnweb-request-group (group &optional server dont-check)
130   (nnweb-possibly-change-server nil server)
131   (when (and group
132              (not (equal group nnweb-group))
133              (not nnweb-ephemeral-p))
134     (let ((info (assoc group nnweb-group-alist)))
135       (when info
136         (setq nnweb-group group)
137         (setq nnweb-type (nth 2 info))
138         (setq nnweb-search (nth 3 info))
139         (unless dont-check
140           (nnweb-read-overview group)))))
141   (unless dont-check
142     (nnweb-request-scan group))
143   (cond
144    ((not nnweb-articles)
145     (nnheader-report 'nnweb "No matching articles"))
146    (t
147     (let ((active (if nnweb-ephemeral-p
148                       (cons (caar nnweb-articles)
149                             (caar (last nnweb-articles)))
150                     (cadr (assoc group nnweb-group-alist)))))
151       (nnheader-report 'nnweb "Opened group %s" group)
152       (nnheader-insert
153        "211 %d %d %d %s\n" (length nnweb-articles)
154        (car active) (cdr active) group)))))
155
156 (deffoo nnweb-close-group (group &optional server)
157   (nnweb-possibly-change-server group server)
158   (when (gnus-buffer-live-p nnweb-buffer)
159     (save-excursion
160       (set-buffer nnweb-buffer)
161       (set-buffer-modified-p nil)
162       (kill-buffer nnweb-buffer)))
163   t)
164
165 (deffoo nnweb-request-article (article &optional group server buffer)
166   (nnweb-possibly-change-server group server)
167   (save-excursion
168     (set-buffer (or buffer nntp-server-buffer))
169     (let* ((header (cadr (assq article nnweb-articles)))
170            (url (and header (mail-header-xref header))))
171       (when (or (and url
172                      (nnweb-fetch-url url))
173                 (and (stringp article)
174                      (nnweb-definition 'id t)
175                      (let ((fetch (nnweb-definition 'id))
176                            art)
177                        (when (string-match "^<\\(.*\\)>$" article)
178                          (setq art (match-string 1 article)))
179                        (and fetch
180                             art
181                             (nnweb-fetch-url
182                              (format fetch article))))))
183         (unless nnheader-callback-function
184           (funcall (nnweb-definition 'article))
185           (nnweb-decode-entities))
186         (nnheader-report 'nnweb "Fetched article %s" article)
187         t))))
188
189 (deffoo nnweb-close-server (&optional server)
190   (when (and (nnweb-server-opened server)
191              (gnus-buffer-live-p nnweb-buffer))
192     (save-excursion
193       (set-buffer nnweb-buffer)
194       (set-buffer-modified-p nil)
195       (kill-buffer nnweb-buffer)))
196   (nnoo-close-server 'nnweb server))
197
198 (deffoo nnweb-request-list (&optional server)
199   (nnweb-possibly-change-server nil server)
200   (save-excursion
201     (set-buffer nntp-server-buffer)
202     (nnmail-generate-active nnweb-group-alist)
203     t))
204
205 (deffoo nnweb-request-update-info (group info &optional server)
206   (nnweb-possibly-change-server group server)
207   ;;(setcar (cddr info) nil)
208   )
209
210 (deffoo nnweb-asynchronous-p ()
211   t)
212
213 (deffoo nnweb-request-create-group (group &optional server args)
214   (nnweb-possibly-change-server nil server)
215   (nnweb-request-delete-group group)
216   (push `(,group ,(cons 1 0) ,@args) nnweb-group-alist)
217   (nnweb-write-active)
218   t)
219
220 (deffoo nnweb-request-delete-group (group &optional force server)
221   (nnweb-possibly-change-server group server)
222   (gnus-pull group nnweb-group-alist t)
223   (nnweb-write-active)
224   (gnus-delete-file (nnweb-overview-file group))
225   t)
226
227 (nnoo-define-skeleton nnweb)
228
229 ;;; Internal functions
230
231 (defun nnweb-read-overview (group)
232   "Read the overview of GROUP and build the map."
233   (when (file-exists-p (nnweb-overview-file group))
234     (with-temp-buffer
235       (nnheader-insert-file-contents (nnweb-overview-file group))
236       (goto-char (point-min))
237       (let (header)
238         (while (not (eobp))
239           (setq header (nnheader-parse-nov))
240           (forward-line 1)
241           (push (list (mail-header-number header)
242                       header (mail-header-xref header))
243                 nnweb-articles)
244           (nnweb-set-hashtb header (car nnweb-articles)))))))
245
246 (defun nnweb-write-overview (group)
247   "Write the overview file for GROUP."
248   (with-temp-file (nnweb-overview-file group)
249     (let ((articles nnweb-articles))
250       (while articles
251         (nnheader-insert-nov (cadr (pop articles)))))))
252
253 (defun nnweb-set-hashtb (header data)
254   (gnus-sethash (nnweb-identifier (mail-header-xref header))
255                 data nnweb-hashtb))
256
257 (defun nnweb-get-hashtb (url)
258   (gnus-gethash (nnweb-identifier url) nnweb-hashtb))
259
260 (defun nnweb-identifier (ident)
261   (funcall (nnweb-definition 'identifier) ident))
262
263 (defun nnweb-overview-file (group)
264   "Return the name of the overview file of GROUP."
265   (nnheader-concat nnweb-directory group ".overview"))
266
267 (defun nnweb-write-active ()
268   "Save the active file."
269   (gnus-make-directory nnweb-directory)
270   (with-temp-file (nnheader-concat nnweb-directory "active")
271     (prin1 `(setq nnweb-group-alist ',nnweb-group-alist) (current-buffer))))
272
273 (defun nnweb-read-active ()
274   "Read the active file."
275   (load (nnheader-concat nnweb-directory "active") t t t))
276
277 (defun nnweb-definition (type &optional noerror)
278   "Return the definition of TYPE."
279   (let ((def (cdr (assq type (assq nnweb-type nnweb-type-definition)))))
280     (when (and (not def)
281                (not noerror))
282       (error "Undefined definition %s" type))
283     def))
284
285 (defun nnweb-possibly-change-server (&optional group server)
286   (nnweb-init server)
287   (when server
288     (unless (nnweb-server-opened server)
289       (nnweb-open-server server)))
290   (unless nnweb-group-alist
291     (nnweb-read-active))
292   (when group
293     (when (and (not nnweb-ephemeral-p)
294                (not (equal group nnweb-group)))
295       (nnweb-request-group group nil t))))
296
297 (defun nnweb-init (server)
298   "Initialize buffers and such."
299   (unless (gnus-buffer-live-p nnweb-buffer)
300     (setq nnweb-buffer
301           (save-excursion
302             (nnheader-set-temp-buffer
303              (format " *nnweb %s %s %s*" nnweb-type nnweb-search server))))))
304
305 (defun nnweb-fetch-url (url)
306   (save-excursion
307     (if (not nnheader-callback-function)
308         (let ((buf (current-buffer)))
309           (save-excursion
310             (set-buffer nnweb-buffer)
311             (erase-buffer)
312             (url-insert-file-contents url)
313             (copy-to-buffer buf (point-min) (point-max))
314             t))
315       (nnweb-url-retrieve-asynch
316        url 'nnweb-callback (current-buffer) nnheader-callback-function)
317       t)))
318
319 (defun nnweb-callback (buffer callback)
320   (when (gnus-buffer-live-p url-working-buffer)
321     (save-excursion
322       (set-buffer url-working-buffer)
323       (funcall (nnweb-definition 'article))
324       (nnweb-decode-entities)
325       (set-buffer buffer)
326       (goto-char (point-max))
327       (insert-buffer-substring url-working-buffer))
328     (funcall callback t)
329     (gnus-kill-buffer url-working-buffer)))
330
331 (defun nnweb-url-retrieve-asynch (url callback &rest data)
332   (let ((url-request-method "GET")
333         (old-asynch url-be-asynchronous)
334         (url-request-data nil)
335         (url-request-extra-headers nil)
336         (url-working-buffer (generate-new-buffer-name " *nnweb*")))
337     (setq-default url-be-asynchronous t)
338     (save-excursion
339       (set-buffer (get-buffer-create url-working-buffer))
340       (setq url-current-callback-data data
341             url-be-asynchronous t
342             url-current-callback-func callback)
343       (url-retrieve url))
344     (setq-default url-be-asynchronous old-asynch)))
345
346 ;;;
347 ;;; DejaNews functions.
348 ;;;
349
350 (defun nnweb-dejanews-create-mapping ()
351   "Perform the search and create an number-to-url alist."
352   (save-excursion
353     (set-buffer nnweb-buffer)
354     (erase-buffer)
355     (when (funcall (nnweb-definition 'search) nnweb-search)
356       (let ((i 0)
357             (more t)
358             (case-fold-search t)
359             (active (or (cadr (assoc nnweb-group nnweb-group-alist))
360                         (cons 1 0)))
361             subject date from
362             map url parse a table group)
363         (while more
364           ;; Go through all the article hits on this page.
365           (goto-char (point-min))
366           (setq parse (w3-parse-buffer (current-buffer))
367                 table (nth 1 (nnweb-parse-find-all 'table parse)))
368           (dolist (row (nth 2 (car (nth 2 table))))
369             (setq a (nnweb-parse-find 'a row)
370                   url (cdr (assq 'href (nth 1 a)))
371                   text (nnweb-text row))
372             (when a
373               (setq subject (nth 2 text)
374                     group (nth 4 text)
375                     date (nth 5 text)
376                     from (nth 6 text))
377               (string-match "\\([0-9]+\\)/\\([0-9]+\\)/\\([0-9]+\\)" date)
378               (setq date (format "%s %s %s"
379                                  (car (rassq (string-to-number
380                                               (match-string 2 date))
381                                              parse-time-months))
382                                  (match-string 3 date) (match-string 1 date)))
383               (incf i)
384               (setq url (concat url "&fmt=text"))
385               (unless (nnweb-get-hashtb url)
386                 (push
387                  (list
388                   (incf (cdr active))
389                   (make-full-mail-header
390                    (cdr active) (concat subject " (" group ")") from date
391                    (concat "<" (nnweb-identifier url) "@dejanews>")
392                    nil 0 0 url))
393                  map)
394                 (nnweb-set-hashtb (cadar map) (car map)))))
395           ;; See whether there is a "Get next 20 hits" button here.
396           (goto-char (point-min))
397           (if (or (not (re-search-forward
398                         "HREF=\"\\([^\"]+\\)\"[<>b]+Next result" nil t))
399                   (>= i nnweb-max-hits))
400               (setq more nil)
401             ;; Yup -- fetch it.
402             (setq more (match-string 1))
403             (erase-buffer)
404             (url-insert-file-contents more)))
405         ;; Return the articles in the right order.
406         (setq nnweb-articles
407               (sort (nconc nnweb-articles map) 'car-less-than-car))))))
408
409 (defun nnweb-dejanews-search (search)
410   (nnweb-insert
411    (concat
412     (nnweb-definition 'address)
413     "?"
414     (nnweb-encode-www-form-urlencoded
415      `(("ST" . "PS")
416        ("svcclass" . "dnyr")
417        ("QRY" . ,search)
418        ("defaultOp" . "AND")
419        ("DBS" . "1")
420        ("OP" . "dnquery.xp")
421        ("LNG" . "ALL")
422        ("maxhits" . "100")
423        ("threaded" . "0")
424        ("format" . "verbose2")
425        ("showsort" . "date")
426        ("agesign" . "1")
427        ("ageweight" . "1")))))
428   t)
429
430 (defun nnweb-dejanewsold-search (search)
431   (nnweb-fetch-form
432    (nnweb-definition 'address)
433    `(("query" . ,search)
434      ("defaultOp" . "AND")
435      ("svcclass" . "dnold")
436      ("maxhits" . "100")
437      ("format" . "verbose2")
438      ("threaded" . "0")
439      ("showsort" . "date")
440      ("agesign" . "1")
441      ("ageweight" . "1")))
442   t)
443
444 (defun nnweb-dejanews-identity (url)
445   "Return an unique identifier based on URL."
446   (if (string-match "AN=\\([0-9]+\\)" url)
447       (match-string 1 url)
448     url))
449
450 ;;;
451 ;;; InReference
452 ;;;
453
454 (defun nnweb-reference-create-mapping ()
455   "Perform the search and create an number-to-url alist."
456   (save-excursion
457     (set-buffer nnweb-buffer)
458     (erase-buffer)
459     (when (funcall (nnweb-definition 'search) nnweb-search)
460       (let ((i 0)
461             (more t)
462             (case-fold-search t)
463             (active (or (cadr (assoc nnweb-group nnweb-group-alist))
464                         (cons 1 0)))
465             Subject Score Date Newsgroups From Message-ID
466             map url)
467         (while more
468           ;; Go through all the article hits on this page.
469           (goto-char (point-min))
470           (search-forward "</pre><hr>" nil t)
471           (delete-region (point-min) (point))
472                                         ;(nnweb-decode-entities)
473           (goto-char (point-min))
474           (while (re-search-forward "^ +[0-9]+\\." nil t)
475             (narrow-to-region
476              (point)
477              (if (re-search-forward "^$" nil t)
478                  (match-beginning 0)
479                (point-max)))
480             (goto-char (point-min))
481             (when (looking-at ".*href=\"\\([^\"]+\\)\"")
482               (setq url (match-string 1)))
483             (nnweb-remove-markup)
484             (goto-char (point-min))
485             (while (search-forward "\t" nil t)
486               (replace-match " "))
487             (goto-char (point-min))
488             (while (re-search-forward "^\\([^:]+\\): \\(.*\\)$" nil t)
489               (set (intern (match-string 1)) (match-string 2)))
490             (widen)
491             (search-forward "</pre>" nil t)
492             (incf i)
493             (unless (nnweb-get-hashtb url)
494               (push
495                (list
496                 (incf (cdr active))
497                 (make-full-mail-header
498                  (cdr active) (concat  "(" Newsgroups ") " Subject) From Date
499                  Message-ID
500                  nil 0 (string-to-int Score) url))
501                map)
502               (nnweb-set-hashtb (cadar map) (car map))))
503           (setq more nil))
504         ;; Return the articles in the right order.
505         (setq nnweb-articles
506               (sort (nconc nnweb-articles map) 'car-less-than-car))))))
507
508 (defun nnweb-reference-wash-article ()
509   (let ((case-fold-search t))
510     (goto-char (point-min))
511     (re-search-forward "^</center><hr>" nil t)
512     (delete-region (point-min) (point))
513     (search-forward "<pre>" nil t)
514     (forward-line -1)
515     (let ((body (point-marker)))
516       (search-forward "</pre>" nil t)
517       (delete-region (point) (point-max))
518       (nnweb-remove-markup)
519       (goto-char (point-min))
520       (while (looking-at " *$")
521         (gnus-delete-line))
522       (narrow-to-region (point-min) body)
523       (while (and (re-search-forward "^$" nil t)
524                   (not (eobp)))
525         (gnus-delete-line))
526       (goto-char (point-min))
527       (while (looking-at "\\(^[^ ]+:\\) *")
528         (replace-match "\\1 " t)
529         (forward-line 1))
530       (goto-char (point-min))
531       (when (re-search-forward "^References:" nil t)
532         (narrow-to-region
533          (point) (if (re-search-forward "^$\\|^[^:]+:" nil t)
534                      (match-beginning 0)
535                    (point-max)))
536         (goto-char (point-min))
537         (while (not (eobp))
538           (unless (looking-at "References")
539             (insert "\t")
540             (forward-line 1)))
541         (goto-char (point-min))
542         (while (search-forward "," nil t)
543           (replace-match " " t t)))
544       (widen)
545       (set-marker body nil))))
546
547 (defun nnweb-reference-search (search)
548   (url-insert-file-contents
549    (concat
550     (nnweb-definition 'address)
551     "?"
552     (nnweb-encode-www-form-urlencoded
553      `(("search" . "advanced")
554        ("querytext" . ,search)
555        ("subj" . "")
556        ("name" . "")
557        ("login" . "")
558        ("host" . "")
559        ("organization" . "")
560        ("groups" . "")
561        ("keywords" . "")
562        ("choice" . "Search")
563        ("startmonth" . "Jul")
564        ("startday" . "25")
565        ("startyear" . "1996")
566        ("endmonth" . "Aug")
567        ("endday" . "24")
568        ("endyear" . "1996")
569        ("mode" . "Quick")
570        ("verbosity" . "Verbose")
571        ("ranking" . "Relevance")
572        ("first" . "1")
573        ("last" . "25")
574        ("score" . "50")))))
575   (setq buffer-file-name nil)
576   t)
577
578 ;;;
579 ;;; Alta Vista
580 ;;;
581
582 (defun nnweb-altavista-create-mapping ()
583   "Perform the search and create an number-to-url alist."
584   (save-excursion
585     (set-buffer nnweb-buffer)
586     (erase-buffer)
587     (let ((part 0))
588       (when (funcall (nnweb-definition 'search) nnweb-search part)
589         (let ((i 0)
590               (more t)
591               (case-fold-search t)
592               (active (or (cadr (assoc nnweb-group nnweb-group-alist))
593                           (cons 1 0)))
594               subject date from id group
595               map url)
596           (while more
597             ;; Go through all the article hits on this page.
598             (goto-char (point-min))
599             (search-forward "<dt>" nil t)
600             (delete-region (point-min) (match-beginning 0))
601             (goto-char (point-min))
602             (while (search-forward "<dt>" nil t)
603               (replace-match "\n<blubb>"))
604             (nnweb-decode-entities)
605             (goto-char (point-min))
606             (while (re-search-forward "<blubb>.*href=\"\\([^\"]+\\)\"><strong>\\([^>]*\\)</strong></a><dd>\\([^-]+\\)- <b>\\([^<]+\\)<.*href=\"news:\\([^\"]+\\)\">.*\">\\(.+\\)</a><P>"
607                                       nil t)
608               (setq url (match-string 1)
609                     subject (match-string 2)
610                     date (match-string 3)
611                     group (match-string 4)
612                     id (concat "<" (match-string 5) ">")
613                     from (match-string 6))
614               (incf i)
615               (unless (nnweb-get-hashtb url)
616                 (push
617                  (list
618                   (incf (cdr active))
619                   (make-full-mail-header
620                    (cdr active) (concat  "(" group ") " subject) from date
621                    id nil 0 0 url))
622                  map)
623                 (nnweb-set-hashtb (cadar map) (car map))))
624             ;; See if we want more.
625             (when (or (not nnweb-articles)
626                       (>= i nnweb-max-hits)
627                       (not (funcall (nnweb-definition 'search)
628                                     nnweb-search (incf part))))
629               (setq more nil)))
630           ;; Return the articles in the right order.
631           (setq nnweb-articles
632                 (sort (nconc nnweb-articles map) 'car-less-than-car)))))))
633
634 (defun nnweb-altavista-wash-article ()
635   (goto-char (point-min))
636   (let ((case-fold-search t))
637     (when (re-search-forward "^<strong>" nil t)
638       (delete-region (point-min) (match-beginning 0)))
639     (goto-char (point-min))
640     (while (looking-at "<strong>\\([^ ]+\\) +</strong> +\\(.*\\)$")
641       (replace-match "\\1: \\2" t)
642       (forward-line 1))
643     (when (re-search-backward "^References:" nil t)
644       (narrow-to-region (point) (progn (forward-line 1) (point)))
645       (goto-char (point-min))
646       (while (re-search-forward "<A.*\\?id@\\([^\"]+\\)\">[0-9]+</A>" nil t)
647         (replace-match "&lt;\\1&gt; " t)))
648     (widen)
649     (nnweb-remove-markup)))
650
651 (defun nnweb-altavista-search (search &optional part)
652   (url-insert-file-contents
653    (concat
654     (nnweb-definition 'address)
655     "?"
656     (nnweb-encode-www-form-urlencoded
657      `(("pg" . "aq")
658        ("what" . "news")
659        ,@(when part `(("stq" . ,(int-to-string (* part 30)))))
660        ("fmt" . "d")
661        ("q" . ,search)
662        ("r" . "")
663        ("d0" . "")
664        ("d1" . "")))))
665   (setq buffer-file-name nil)
666   t)
667
668 ;;;
669 ;;; General web/w3 interface utility functions
670 ;;;
671
672 (defun nnweb-insert-html (parse)
673   "Insert HTML based on a w3 parse tree."
674   (if (stringp parse)
675       (insert parse)
676     (insert "<" (symbol-name (car parse)) " ")
677     (insert (mapconcat
678              (lambda (param)
679                (concat (symbol-name (car param)) "="
680                        (prin1-to-string
681                         (if (consp (cdr param))
682                             (cadr param)
683                           (cdr param)))))
684              (nth 1 parse)
685              " "))
686     (insert ">\n")
687     (mapcar 'nnweb-insert-html (nth 2 parse))
688     (insert "</" (symbol-name (car parse)) ">\n")))
689
690 (defun nnweb-encode-www-form-urlencoded (pairs)
691   "Return PAIRS encoded for forms."
692   (mapconcat
693    (function
694     (lambda (data)
695       (concat (w3-form-encode-xwfu (car data)) "="
696               (w3-form-encode-xwfu (cdr data)))))
697    pairs "&"))
698
699 (defun nnweb-fetch-form (url pairs)
700   "Fetch a form from URL with PAIRS as the data."
701   (let ((url-request-data (nnweb-encode-www-form-urlencoded pairs))
702         (url-request-method "POST")
703         (url-request-extra-headers
704          '(("Content-type" . "application/x-www-form-urlencoded"))))
705     (url-insert-file-contents url)
706     (setq buffer-file-name nil))
707   t)
708
709 (defun nnweb-decode-entities ()
710   "Decode all HTML entities."
711   (goto-char (point-min))
712   (while (re-search-forward "&\\([a-z]+\\);" nil t)
713     (replace-match (char-to-string (or (cdr (assq (intern (match-string 1))
714                                                   w3-html-entities))
715                                        ?#))
716                    t t)))
717
718 (defun nnweb-remove-markup ()
719   "Remove all HTML markup, leaving just plain text."
720   (goto-char (point-min))
721   (while (search-forward "<!--" nil t)
722     (delete-region (match-beginning 0)
723                    (or (search-forward "-->" nil t)
724                        (point-max))))
725   (goto-char (point-min))
726   (while (re-search-forward "<[^>]+>" nil t)
727     (replace-match "" t t)))
728
729 (defun nnweb-insert (url)
730   "Insert the contents from an URL in the current buffer."
731   (let ((name buffer-file-name))
732     (url-insert-file-contents url)
733     (setq buffer-file-name name)))
734
735 (defun nnweb-parse-find (type parse &optional maxdepth)
736   "Find the element of TYPE in PARSE."
737   (catch 'found
738     (nnweb-parse-find-1 type parse maxdepth)))
739
740 (defun nnweb-parse-find-1 (type contents maxdepth)
741   (when (or (null maxdepth)
742             (not (zerop maxdepth)))
743     (when (consp contents)
744       (when (eq (car contents) type)
745         (throw 'found contents))
746       (when (listp (cdr contents))
747         (dolist (element contents)
748           (when (consp element)
749             (nnweb-parse-find-1 type element
750                                 (and maxdepth (1- maxdepth)))))))))
751
752 (defun nnweb-parse-find-all (type parse)
753   "Find all elements of TYPE in PARSE."
754   (catch 'found
755     (nnweb-parse-find-all-1 type parse)))
756
757 (defun nnweb-parse-find-all-1 (type contents)
758   (let (result)
759     (when (consp contents)
760       (if (eq (car contents) type)
761           (push contents result)
762         (when (listp (cdr contents))
763           (dolist (element contents)
764             (when (consp element)
765               (setq result
766                     (nconc result (nnweb-parse-find-all-1 type element))))))))
767     result))
768
769 (defvar nnweb-text)
770 (defun nnweb-text (parse)
771   "Return a list of text contents in PARSE."
772   (let ((nnweb-text nil))
773     (nnweb-text-1 parse)
774     (nreverse nnweb-text)))
775
776 (defun nnweb-text-1 (contents)
777   (dolist (element contents)
778     (if (stringp element)
779         (push element nnweb-text)
780       (when (and (consp element)
781                  (listp (cdr element)))
782         (nnweb-text-1 element)))))
783
784 (provide 'nnweb)
785
786 ;;; nnweb.el ends here