delete_temp_file fix
[bpt/emacs.git] / lisp / gnus / nnweb.el
CommitLineData
eec82323 1;;; nnweb.el --- retrieving articles via web search engines
e84b4b86 2
ba318903 3;; Copyright (C) 1996-2014 Free Software Foundation, Inc.
eec82323 4
6748645f 5;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
eec82323
LMI
6;; Keywords: news
7
8;; This file is part of GNU Emacs.
9
5e809f55 10;; GNU Emacs is free software: you can redistribute it and/or modify
eec82323 11;; it under the terms of the GNU General Public License as published by
5e809f55
GM
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
eec82323
LMI
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
5e809f55 21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
eec82323
LMI
22
23;;; Commentary:
24
eec82323
LMI
25;;; Code:
26
5ab7173c
RS
27(eval-when-compile (require 'cl))
28
eec82323
LMI
29(require 'nnoo)
30(require 'message)
31(require 'gnus-util)
32(require 'gnus)
eec82323 33(require 'nnmail)
16409b0b 34(require 'mm-util)
23f87bed
MB
35(require 'mm-url)
36(eval-and-compile
0d5dc4a5 37 (ignore-errors
23f87bed 38 (require 'url)))
eec82323
LMI
39
40(nnoo-declare nnweb)
41
42(defvoo nnweb-directory (nnheader-concat gnus-directory "nnweb/")
43 "Where nnweb will save its files.")
44
95fa1ff7 45(defvoo nnweb-type 'google
6748645f 46 "What search engine type is being used.
23f87bed 47Valid types include `google', `dejanews', and `gmane'.")
eec82323 48
16409b0b 49(defvar nnweb-type-definition
23f87bed 50 '((google
4a2358e9 51 (id . "http://www.google.com/groups?as_umsgid=%s&hl=en&dmode=source")
46e8fe3d 52 (result . "http://groups.google.com/group/%s/msg/%s?dmode=source")
4a2358e9 53 (article . nnweb-google-wash-article)
95fa1ff7
SZ
54 (reference . identity)
55 (map . nnweb-google-create-mapping)
56 (search . nnweb-google-search)
4a2358e9
MB
57 (address . "http://groups.google.com/groups")
58 (base . "http://groups.google.com")
95fa1ff7
SZ
59 (identifier . nnweb-google-identity))
60 (dejanews ;; alias of google
46e8fe3d
MB
61 (id . "http://www.google.com/groups?as_umsgid=%s&hl=en&dmode=source")
62 (result . "http://groups.google.com/group/%s/msg/%s?dmode=source")
63 (article . nnweb-google-wash-article)
95fa1ff7
SZ
64 (reference . identity)
65 (map . nnweb-google-create-mapping)
66 (search . nnweb-google-search)
67 (address . "http://groups.google.com/groups")
5f5475ac 68 (base . "http://groups.google.com")
95fa1ff7 69 (identifier . nnweb-google-identity))
23f87bed
MB
70 (gmane
71 (article . nnweb-gmane-wash-article)
72 (id . "http://gmane.org/view.php?group=%s")
73 (reference . identity)
74 (map . nnweb-gmane-create-mapping)
75 (search . nnweb-gmane-search)
719120ef 76 (address . "http://search.gmane.org/nov.php")
23f87bed 77 (identifier . nnweb-gmane-identity)))
eec82323
LMI
78 "Type-definition alist.")
79
80(defvoo nnweb-search nil
23f87bed 81 "Search string to feed to Google.")
eec82323 82
6748645f 83(defvoo nnweb-max-hits 999
eec82323
LMI
84 "Maximum number of hits to display.")
85
86(defvoo nnweb-ephemeral-p nil
87 "Whether this nnweb server is ephemeral.")
88
89;;; Internal variables
90
91(defvoo nnweb-articles nil)
92(defvoo nnweb-buffer nil)
719120ef 93(defvoo nnweb-group-alist nil)
eec82323
LMI
94(defvoo nnweb-group nil)
95(defvoo nnweb-hashtb nil)
96
97;;; Interface functions
98
99(nnoo-define-basics nnweb)
100
101(deffoo nnweb-retrieve-headers (articles &optional group server fetch-old)
102 (nnweb-possibly-change-server group server)
20a673b2 103 (with-current-buffer nntp-server-buffer
eec82323
LMI
104 (erase-buffer)
105 (let (article header)
16409b0b
GM
106 (mm-with-unibyte-current-buffer
107 (while (setq article (pop articles))
108 (when (setq header (cadr (assq article nnweb-articles)))
109 (nnheader-insert-nov header))))
eec82323
LMI
110 'nov)))
111
112(deffoo nnweb-request-scan (&optional group server)
113 (nnweb-possibly-change-server group server)
95fa1ff7 114 (if nnweb-ephemeral-p
46e8fe3d
MB
115 (setq nnweb-hashtb (gnus-make-hashtable 4095))
116 (unless nnweb-articles
117 (nnweb-read-overview group)))
eec82323
LMI
118 (funcall (nnweb-definition 'map))
119 (unless nnweb-ephemeral-p
120 (nnweb-write-active)
121 (nnweb-write-overview group)))
122
286c4fc2 123(deffoo nnweb-request-group (group &optional server dont-check info)
46e8fe3d
MB
124 (nnweb-possibly-change-server group server)
125 (unless (or nnweb-ephemeral-p
6203370b
MB
126 dont-check
127 nnweb-articles)
46e8fe3d 128 (nnweb-read-overview group))
eec82323
LMI
129 (cond
130 ((not nnweb-articles)
131 (nnheader-report 'nnweb "No matching articles"))
132 (t
133 (let ((active (if nnweb-ephemeral-p
134 (cons (caar nnweb-articles)
135 (caar (last nnweb-articles)))
136 (cadr (assoc group nnweb-group-alist)))))
137 (nnheader-report 'nnweb "Opened group %s" group)
138 (nnheader-insert
139 "211 %d %d %d %s\n" (length nnweb-articles)
140 (car active) (cdr active) group)))))
141
142(deffoo nnweb-close-group (group &optional server)
143 (nnweb-possibly-change-server group server)
144 (when (gnus-buffer-live-p nnweb-buffer)
20a673b2 145 (with-current-buffer nnweb-buffer
eec82323
LMI
146 (set-buffer-modified-p nil)
147 (kill-buffer nnweb-buffer)))
148 t)
149
150(deffoo nnweb-request-article (article &optional group server buffer)
151 (nnweb-possibly-change-server group server)
20a673b2 152 (with-current-buffer (or buffer nntp-server-buffer)
eec82323
LMI
153 (let* ((header (cadr (assq article nnweb-articles)))
154 (url (and header (mail-header-xref header))))
155 (when (or (and url
16409b0b 156 (mm-with-unibyte-current-buffer
23f87bed 157 (mm-url-insert url)))
eec82323
LMI
158 (and (stringp article)
159 (nnweb-definition 'id t)
160 (let ((fetch (nnweb-definition 'id))
95fa1ff7 161 art active)
eec82323
LMI
162 (when (string-match "^<\\(.*\\)>$" article)
163 (setq art (match-string 1 article)))
95fa1ff7 164 (when (and fetch art)
7ce31649
MB
165 (setq url (format fetch
166 (mm-url-form-encode-xwfu art)))
95fa1ff7 167 (mm-with-unibyte-current-buffer
23f87bed 168 (mm-url-insert url))
95fa1ff7
SZ
169 (if (nnweb-definition 'reference t)
170 (setq article
171 (funcall (nnweb-definition
172 'reference) article)))))))
eec82323 173 (unless nnheader-callback-function
95fa1ff7 174 (funcall (nnweb-definition 'article)))
eec82323 175 (nnheader-report 'nnweb "Fetched article %s" article)
16409b0b 176 (cons group (and (numberp article) article))))))
eec82323
LMI
177
178(deffoo nnweb-close-server (&optional server)
179 (when (and (nnweb-server-opened server)
180 (gnus-buffer-live-p nnweb-buffer))
20a673b2 181 (with-current-buffer nnweb-buffer
eec82323
LMI
182 (set-buffer-modified-p nil)
183 (kill-buffer nnweb-buffer)))
184 (nnoo-close-server 'nnweb server))
185
186(deffoo nnweb-request-list (&optional server)
187 (nnweb-possibly-change-server nil server)
20a673b2 188 (with-current-buffer nntp-server-buffer
46e8fe3d 189 (nnmail-generate-active (list (assoc server nnweb-group-alist)))
eec82323
LMI
190 t))
191
b1ae92ba 192(deffoo nnweb-request-update-info (group info &optional server))
eec82323
LMI
193
194(deffoo nnweb-asynchronous-p ()
23f87bed 195 nil)
eec82323
LMI
196
197(deffoo nnweb-request-create-group (group &optional server args)
198 (nnweb-possibly-change-server nil server)
199 (nnweb-request-delete-group group)
46e8fe3d 200 (push `(,group ,(cons 1 0)) nnweb-group-alist)
eec82323
LMI
201 (nnweb-write-active)
202 t)
203
204(deffoo nnweb-request-delete-group (group &optional force server)
205 (nnweb-possibly-change-server group server)
36d3245f 206 (gnus-alist-pull group nnweb-group-alist t)
16409b0b 207 (nnweb-write-active)
eec82323
LMI
208 (gnus-delete-file (nnweb-overview-file group))
209 t)
210
211(nnoo-define-skeleton nnweb)
212
213;;; Internal functions
214
215(defun nnweb-read-overview (group)
216 "Read the overview of GROUP and build the map."
217 (when (file-exists-p (nnweb-overview-file group))
16409b0b 218 (mm-with-unibyte-buffer
eec82323
LMI
219 (nnheader-insert-file-contents (nnweb-overview-file group))
220 (goto-char (point-min))
221 (let (header)
222 (while (not (eobp))
223 (setq header (nnheader-parse-nov))
224 (forward-line 1)
225 (push (list (mail-header-number header)
226 header (mail-header-xref header))
227 nnweb-articles)
228 (nnweb-set-hashtb header (car nnweb-articles)))))))
229
230(defun nnweb-write-overview (group)
231 "Write the overview file for GROUP."
16409b0b 232 (with-temp-file (nnweb-overview-file group)
eec82323
LMI
233 (let ((articles nnweb-articles))
234 (while articles
235 (nnheader-insert-nov (cadr (pop articles)))))))
236
237(defun nnweb-set-hashtb (header data)
238 (gnus-sethash (nnweb-identifier (mail-header-xref header))
239 data nnweb-hashtb))
240
241(defun nnweb-get-hashtb (url)
242 (gnus-gethash (nnweb-identifier url) nnweb-hashtb))
243
244(defun nnweb-identifier (ident)
245 (funcall (nnweb-definition 'identifier) ident))
246
247(defun nnweb-overview-file (group)
248 "Return the name of the overview file of GROUP."
249 (nnheader-concat nnweb-directory group ".overview"))
250
251(defun nnweb-write-active ()
252 "Save the active file."
16409b0b
GM
253 (gnus-make-directory nnweb-directory)
254 (with-temp-file (nnheader-concat nnweb-directory "active")
eec82323
LMI
255 (prin1 `(setq nnweb-group-alist ',nnweb-group-alist) (current-buffer))))
256
257(defun nnweb-read-active ()
258 "Read the active file."
259 (load (nnheader-concat nnweb-directory "active") t t t))
260
261(defun nnweb-definition (type &optional noerror)
262 "Return the definition of TYPE."
263 (let ((def (cdr (assq type (assq nnweb-type nnweb-type-definition)))))
264 (when (and (not def)
265 (not noerror))
266 (error "Undefined definition %s" type))
267 def))
268
269(defun nnweb-possibly-change-server (&optional group server)
eec82323
LMI
270 (when server
271 (unless (nnweb-server-opened server)
46e8fe3d
MB
272 (nnweb-open-server server))
273 (nnweb-init server))
eec82323
LMI
274 (unless nnweb-group-alist
275 (nnweb-read-active))
95fa1ff7
SZ
276 (unless nnweb-hashtb
277 (setq nnweb-hashtb (gnus-make-hashtable 4095)))
eec82323 278 (when group
46e8fe3d 279 (setq nnweb-group group)))
eec82323
LMI
280
281(defun nnweb-init (server)
282 "Initialize buffers and such."
283 (unless (gnus-buffer-live-p nnweb-buffer)
284 (setq nnweb-buffer
3b728e95
SM
285 (save-current-buffer
286 (nnheader-set-temp-buffer
287 (format " *nnweb %s %s %s*"
288 nnweb-type nnweb-search server))
289 (mm-disable-multibyte)
290 (current-buffer)))))
eec82323 291
95fa1ff7 292;;;
4a2358e9 293;;; groups.google.com
95fa1ff7
SZ
294;;;
295
296(defun nnweb-google-wash-article ()
4a2358e9 297 ;; We have Google's masked e-mail addresses here. :-/
719120ef 298 (let ((case-fold-search t)
0565caeb
MB
299 (start-re "<pre>[\r\n ]*")
300 (end-re "[\r\n ]*</pre>"))
95fa1ff7 301 (goto-char (point-min))
d752cf53
MB
302 (if (save-excursion
303 (or (re-search-forward "The requested message.*could not be found."
304 nil t)
719120ef
MB
305 (not (and (re-search-forward start-re nil t)
306 (re-search-forward end-re nil t)))))
d752cf53
MB
307 ;; FIXME: Don't know how to indicate "not found".
308 ;; Should this function throw an error? --rsteib
309 (progn
310 (gnus-message 3 "Requested article not found")
311 (erase-buffer))
312 (delete-region (point-min)
719120ef 313 (re-search-forward start-re))
d752cf53 314 (goto-char (point-min))
719120ef
MB
315 (delete-region (progn
316 (re-search-forward end-re)
317 (match-beginning 0))
d752cf53
MB
318 (point-max))
319 (mm-url-decode-entities))))
95fa1ff7
SZ
320
321(defun nnweb-google-parse-1 (&optional Message-ID)
46e8fe3d 322 "Parse search result in current buffer."
95fa1ff7
SZ
323 (let ((i 0)
324 (case-fold-search t)
325 (active (cadr (assoc nnweb-group nnweb-group-alist)))
326 Subject Score Date Newsgroups From
327 map url mid)
328 (unless active
46e8fe3d 329 (push (list nnweb-group (setq active (cons 1 0)))
95fa1ff7
SZ
330 nnweb-group-alist))
331 ;; Go through all the article hits on this page.
332 (goto-char (point-min))
46e8fe3d
MB
333 (while
334 (re-search-forward
335 "a +href=\"/group/\\([^>\"]+\\)/browse_thread/[^>]+#\\([0-9a-f]+\\)"
336 nil t)
337 (setq Newsgroups (match-string-no-properties 1)
338 ;; Note: Starting with Google Groups 2, `mid' is a Google-internal
339 ;; ID, not a proper Message-ID.
340 mid (match-string-no-properties 2)
debad045 341 url (format
46e8fe3d 342 (nnweb-definition 'result) Newsgroups mid))
95fa1ff7
SZ
343 (narrow-to-region (search-forward ">" nil t)
344 (search-forward "</a>" nil t))
23f87bed
MB
345 (mm-url-remove-markup)
346 (mm-url-decode-entities)
95fa1ff7
SZ
347 (setq Subject (buffer-string))
348 (goto-char (point-max))
349 (widen)
46e8fe3d 350 (narrow-to-region (point)
c91f4b83 351 (search-forward "</table" nil t))
46e8fe3d
MB
352
353 (mm-url-remove-markup)
354 (mm-url-decode-entities)
c91f4b83
MB
355 (goto-char (point-max))
356 (when
357 (re-search-backward
14e8de0c 358 "^\\(?:\\(\\w+\\) \\([0-9]+\\)\\|\\S-+\\)\\(?: \\([0-9]\\{4\\}\\)\\)? by ?\\(.*\\)"
c91f4b83 359 nil t)
aa260d63
MB
360 (setq Date (if (match-string 1)
361 (format "%s %s 00:00:00 %s"
362 (match-string 1)
363 (match-string 2)
364 (or (match-string 3)
0bfcf5c5 365 (format-time-string "%Y")))
aa260d63 366 (current-time-string)))
c91f4b83 367 (setq From (match-string 4)))
46e8fe3d 368 (widen)
95fa1ff7
SZ
369 (incf i)
370 (unless (nnweb-get-hashtb url)
371 (push
372 (list
373 (incf (cdr active))
374 (make-full-mail-header
375 (cdr active) (if Newsgroups
376 (concat "(" Newsgroups ") " Subject)
377 Subject)
378 From Date (or Message-ID mid)
379 nil 0 0 url))
380 map)
381 (nnweb-set-hashtb (cadar map) (car map))))
382 map))
383
384(defun nnweb-google-reference (id)
385 (let ((map (nnweb-google-parse-1 id)) header)
386 (setq nnweb-articles
387 (nconc nnweb-articles map))
388 (when (setq header (cadar map))
389 (mm-with-unibyte-current-buffer
23f87bed 390 (mm-url-insert (mail-header-xref header)))
95fa1ff7
SZ
391 (caar map))))
392
393(defun nnweb-google-create-mapping ()
debad045 394 "Perform the search and create a number-to-url alist."
20a673b2 395 (with-current-buffer nnweb-buffer
95fa1ff7 396 (erase-buffer)
719120ef 397 (nnheader-message 7 "Searching google...")
95fa1ff7 398 (when (funcall (nnweb-definition 'search) nnweb-search)
23f87bed
MB
399 (let ((more t)
400 (i 0))
95fa1ff7
SZ
401 (while more
402 (setq nnweb-articles
403 (nconc nnweb-articles (nnweb-google-parse-1)))
23f87bed
MB
404 ;; Check if there are more articles to fetch
405 (goto-char (point-min))
406 (incf i 100)
407 (if (or (not (re-search-forward
bd876f90 408 "<a [^>]+href=\"\n?\\([^>\" \n\t]+\\)[^<]*<img[^>]+src=[^>]+next"
719120ef 409 nil t))
23f87bed
MB
410 (>= i nnweb-max-hits))
411 (setq more nil)
412 ;; Yup, there are more articles
5f5475ac 413 (setq more (concat (nnweb-definition 'base) (match-string 1)))
23f87bed
MB
414 (when more
415 (erase-buffer)
719120ef 416 (nnheader-message 7 "Searching google...(%d)" i)
23f87bed 417 (mm-url-insert more))))
95fa1ff7 418 ;; Return the articles in the right order.
719120ef 419 (nnheader-message 7 "Searching google...done")
95fa1ff7
SZ
420 (setq nnweb-articles
421 (sort nnweb-articles 'car-less-than-car))))))
422
423(defun nnweb-google-search (search)
23f87bed 424 (mm-url-insert
95fa1ff7
SZ
425 (concat
426 (nnweb-definition 'address)
427 "?"
23f87bed 428 (mm-url-encode-www-form-urlencoded
95fa1ff7 429 `(("q" . ,search)
7ce31649
MB
430 ("num" . ,(number-to-string
431 (min 100 nnweb-max-hits)))
95fa1ff7 432 ("hq" . "")
5f5475ac 433 ("hl" . "en")
95fa1ff7
SZ
434 ("lr" . "")
435 ("safe" . "off")
46e8fe3d
MB
436 ("sites" . "groups")
437 ("filter" . "0")))))
95fa1ff7
SZ
438 t)
439
440(defun nnweb-google-identity (url)
441 "Return an unique identifier based on URL."
442 (if (string-match "selm=\\([^ &>]+\\)" url)
443 (match-string 1 url)
444 url))
445
23f87bed
MB
446;;;
447;;; gmane.org
448;;;
449(defun nnweb-gmane-create-mapping ()
450 "Perform the search and create a number-to-url alist."
20a673b2 451 (with-current-buffer nnweb-buffer
719120ef
MB
452 (let ((case-fold-search t)
453 (active (or (cadr (assoc nnweb-group nnweb-group-alist))
454 (cons 1 0)))
455 map)
456 (erase-buffer)
457 (nnheader-message 7 "Searching Gmane..." )
458 (when (funcall (nnweb-definition 'search) nnweb-search)
23f87bed 459 (goto-char (point-min))
719120ef
MB
460 ;; Skip the status line
461 (forward-line 1)
462 ;; Thanks to Olly Betts we now have NOV lines in our buffer!
463 (while (not (eobp))
464 (unless (or (eolp) (looking-at "\x0d"))
465 (let ((header (nnheader-parse-nov)))
466 (let ((xref (mail-header-xref header))
467 (from (mail-header-from header))
468 (subject (mail-header-subject header))
469 (rfc2047-encoding-type 'mime))
ba361211 470 (when (string-match " \\([^:]+\\)[:/]\\([0-9]+\\)" xref)
719120ef
MB
471 (mail-header-set-xref
472 header
473 (format "http://article.gmane.org/%s/%s/raw"
474 (match-string 1 xref)
475 (match-string 2 xref))))
476
477 ;; Add host part to gmane-encrypted addresses
478 (when (string-match "@$" from)
479 (mail-header-set-from header
480 (concat from "public.gmane.org")))
481
482 (mail-header-set-subject header
483 (rfc2047-encode-string subject))
484
485 (unless (nnweb-get-hashtb (mail-header-xref header))
ba361211
MB
486 (mail-header-set-number header (incf (cdr active)))
487 (push (list (mail-header-number header) header) map)
719120ef
MB
488 (nnweb-set-hashtb (cadar map) (car map))))))
489 (forward-line 1)))
490 (nnheader-message 7 "Searching Gmane...done")
491 (setq nnweb-articles
492 (sort (nconc nnweb-articles map) 'car-less-than-car)))))
23f87bed
MB
493
494(defun nnweb-gmane-wash-article ()
495 (let ((case-fold-search t))
496 (goto-char (point-min))
719120ef
MB
497 (when (search-forward "<!--X-Head-of-Message-->" nil t)
498 (delete-region (point-min) (point))
499 (goto-char (point-min))
500 (while (looking-at "^<li><em>\\([^ ]+\\)</em>.*</li>")
501 (replace-match "\\1\\2" t)
502 (forward-line 1))
503 (mm-url-remove-markup))))
23f87bed
MB
504
505(defun nnweb-gmane-search (search)
506 (mm-url-insert
507 (concat
508 (nnweb-definition 'address)
509 "?"
510 (mm-url-encode-www-form-urlencoded
719120ef 511 `(("query" . ,search)
01c52d31
MB
512 ("HITSPERPAGE" . ,(number-to-string nnweb-max-hits))
513 ;;("TOPDOC" . "1000")
514 ))))
23f87bed 515 (setq buffer-file-name nil)
765d4319 516 (unless (featurep 'xemacs) (set-buffer-multibyte t))
719120ef 517 (mm-decode-coding-region (point-min) (point-max) 'utf-8)
23f87bed
MB
518 t)
519
23f87bed
MB
520(defun nnweb-gmane-identity (url)
521 "Return a unique identifier based on URL."
522 (if (string-match "group=\\(.+\\)" url)
523 (match-string 1 url)
524 url))
525
16409b0b 526;;;
4d2226bf 527;;; General web interface utility functions
16409b0b
GM
528;;;
529
530(defun nnweb-insert-html (parse)
531 "Insert HTML based on a w3 parse tree."
532 (if (stringp parse)
944c87e0
SM
533 ;; We used to call nnheader-string-as-multibyte here, but it cannot
534 ;; be right, so I removed it. If a bug shows up because of this change,
535 ;; please do not blindly revert the change, but help me find the real
536 ;; cause of the bug instead. --Stef
537 (insert parse)
16409b0b
GM
538 (insert "<" (symbol-name (car parse)) " ")
539 (insert (mapconcat
540 (lambda (param)
541 (concat (symbol-name (car param)) "="
542 (prin1-to-string
543 (if (consp (cdr param))
544 (cadr param)
545 (cdr param)))))
546 (nth 1 parse)
547 " "))
548 (insert ">\n")
01c52d31 549 (mapc 'nnweb-insert-html (nth 2 parse))
16409b0b
GM
550 (insert "</" (symbol-name (car parse)) ">\n")))
551
16409b0b
GM
552(defun nnweb-parse-find (type parse &optional maxdepth)
553 "Find the element of TYPE in PARSE."
554 (catch 'found
555 (nnweb-parse-find-1 type parse maxdepth)))
556
557(defun nnweb-parse-find-1 (type contents maxdepth)
558 (when (or (null maxdepth)
559 (not (zerop maxdepth)))
560 (when (consp contents)
561 (when (eq (car contents) type)
562 (throw 'found contents))
563 (when (listp (cdr contents))
564 (dolist (element contents)
565 (when (consp element)
566 (nnweb-parse-find-1 type element
567 (and maxdepth (1- maxdepth)))))))))
568
569(defun nnweb-parse-find-all (type parse)
570 "Find all elements of TYPE in PARSE."
571 (catch 'found
572 (nnweb-parse-find-all-1 type parse)))
573
574(defun nnweb-parse-find-all-1 (type contents)
575 (let (result)
576 (when (consp contents)
577 (if (eq (car contents) type)
578 (push contents result)
579 (when (listp (cdr contents))
580 (dolist (element contents)
581 (when (consp element)
582 (setq result
583 (nconc result (nnweb-parse-find-all-1 type element))))))))
584 result))
585
586(defvar nnweb-text)
587(defun nnweb-text (parse)
588 "Return a list of text contents in PARSE."
589 (let ((nnweb-text nil))
590 (nnweb-text-1 parse)
591 (nreverse nnweb-text)))
592
593(defun nnweb-text-1 (contents)
594 (dolist (element contents)
595 (if (stringp element)
596 (push element nnweb-text)
597 (when (and (consp element)
598 (listp (cdr element)))
599 (nnweb-text-1 element)))))
600
eec82323
LMI
601(provide 'nnweb)
602
603;;; nnweb.el ends here