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