rm not definition
[bpt/emacs.git] / lisp / net / shr.el
CommitLineData
367f7f81
LMI
1;;; shr.el --- Simple HTML Renderer
2
ba318903 3;; Copyright (C) 2010-2014 Free Software Foundation, Inc.
367f7f81
LMI
4
5;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6;; Keywords: html
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software: you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation, either version 3 of the License, or
13;; (at your option) any later version.
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
21;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
22
23;;; Commentary:
24
25;; This package takes a HTML parse tree (as provided by
26;; libxml-parse-html-region) and renders it in the current buffer. It
27;; does not do CSS, JavaScript or anything advanced: It's geared
28;; towards rendering typical short snippets of HTML, like what you'd
29;; find in HTML email and the like.
30
31;;; Code:
32
3d319c8f 33(eval-when-compile (require 'cl))
7545bd25 34(eval-when-compile (require 'url)) ;For url-filename's setf handler.
71e691a5
G
35(require 'browse-url)
36
870409d4
G
37(defgroup shr nil
38 "Simple HTML Renderer"
2bed3f04 39 :version "24.1"
0ebd92a3 40 :group 'hypermedia)
870409d4
G
41
42(defcustom shr-max-image-proportion 0.9
43 "How big pictures displayed are in relation to the window they're in.
44A value of 0.7 means that they are allowed to take up 70% of the
45width and height of the window. If they are larger than this,
46and Emacs supports it, then the images will be rescaled down to
47fit these criteria."
48 :version "24.1"
49 :group 'shr
50 :type 'float)
51
52(defcustom shr-blocked-images nil
53 "Images that have URLs matching this regexp will be blocked."
54 :version "24.1"
55 :group 'shr
a931698a 56 :type '(choice (const nil) regexp))
870409d4 57
924d6997
G
58(defcustom shr-table-horizontal-line nil
59 "Character used to draw horizontal table lines.
60If nil, don't draw horizontal table lines."
d3098750 61 :group 'shr
af1c6c84 62 :type '(choice (const nil) character))
d3098750 63
e37df674 64(defcustom shr-table-vertical-line ?\s
d3098750 65 "Character used to draw vertical table lines."
afba0c4b 66 :group 'shr
030158f3 67 :type 'character)
afba0c4b 68
e37df674 69(defcustom shr-table-corner ?\s
d3098750 70 "Character used to draw table corners."
6b7df8d3 71 :group 'shr
030158f3 72 :type 'character)
6b7df8d3
G
73
74(defcustom shr-hr-line ?-
d3098750 75 "Character used to draw hr lines."
afba0c4b 76 :group 'shr
030158f3 77 :type 'character)
afba0c4b 78
d0e0de31 79(defcustom shr-width fill-column
bb7f5cbc
G
80 "Frame width to use for rendering.
81May either be an integer specifying a fixed width in characters,
82or nil, meaning that the full width of the window should be
83used."
84 :type '(choice (integer :tag "Fixed width in characters")
85 (const :tag "Use the width of the window" nil))
d0e0de31
JD
86 :group 'shr)
87
c74cb344
G
88(defcustom shr-bullet "* "
89 "Bullet used for unordered lists.
90Alternative suggestions are:
91- \" \"
92- \" \""
bb098075 93 :version "24.4"
c74cb344
G
94 :type 'string
95 :group 'shr)
96
0ebd92a3
LMI
97(defcustom shr-external-browser 'browse-url-default-browser
98 "Function used to launch an external browser."
99 :version "24.4"
100 :group 'shr
101 :type 'function)
102
8a73034e 103(defcustom shr-image-animate t
b70257b0 104 "Non nil means that images that can be animated will be."
8a73034e
IK
105 :version "24.4"
106 :group 'shr
107 :type 'boolean)
108
130e977f
LMI
109(defvar shr-content-function nil
110 "If bound, this should be a function that will return the content.
111This is used for cid: URLs, and the function is called with the
112cid: URL as the argument.")
113
b9bdaf74
KY
114(defvar shr-put-image-function 'shr-put-image
115 "Function called to put image and alt string.")
116
6eee2678
LMI
117(defface shr-strike-through '((t (:strike-through t)))
118 "Font for <s> elements."
119 :group 'shr)
120
f8774e35 121(defface shr-link
7ef1d634 122 '((t (:inherit link)))
df26ce09 123 "Font for link elements."
c2f51e23
G
124 :group 'shr)
125
66627fa9
G
126;;; Internal variables.
127
870409d4
G
128(defvar shr-folding-mode nil)
129(defvar shr-state nil)
130(defvar shr-start nil)
a41c2e6d 131(defvar shr-indentation 0)
130e977f 132(defvar shr-inhibit-images nil)
66627fa9 133(defvar shr-list-mode nil)
3d319c8f 134(defvar shr-content-cache nil)
83ffd571 135(defvar shr-kinsoku-shorten nil)
99e65b2d 136(defvar shr-table-depth 0)
04db63bc 137(defvar shr-stylesheet nil)
dbd5ffad 138(defvar shr-base nil)
728518c3 139(defvar shr-ignore-cache nil)
2644071e 140(defvar shr-external-rendering-functions nil)
c74cb344 141(defvar shr-target-id nil)
be2aa135 142(defvar shr-inhibit-decoration nil)
924d6997 143(defvar shr-table-separator-length 1)
870409d4 144
71e691a5
G
145(defvar shr-map
146 (let ((map (make-sparse-keymap)))
147 (define-key map "a" 'shr-show-alt-text)
148 (define-key map "i" 'shr-browse-image)
89b163db 149 (define-key map "z" 'shr-zoom-image)
6952100d
LI
150 (define-key map [?\t] 'shr-next-link)
151 (define-key map [?\M-\t] 'shr-previous-link)
970ad972 152 (define-key map [follow-link] 'mouse-face)
1211de50 153 (define-key map [mouse-2] 'shr-browse-url)
71e691a5 154 (define-key map "I" 'shr-insert-image)
16f74f10 155 (define-key map "w" 'shr-copy-url)
2d756ae0 156 (define-key map "u" 'shr-copy-url)
71e691a5 157 (define-key map "v" 'shr-browse-url)
cdf1fca4 158 (define-key map "o" 'shr-save-contents)
71e691a5
G
159 (define-key map "\r" 'shr-browse-url)
160 map))
161
66627fa9 162;; Public functions and commands.
0143b8a3
GM
163(declare-function libxml-parse-html-region "xml.c"
164 (start end &optional base-url))
66627fa9 165
7b953864
SM
166(defun shr-render-buffer (buffer)
167 "Display the HTML rendering of the current buffer."
168 (interactive (list (current-buffer)))
0143b8a3
GM
169 (or (fboundp 'libxml-parse-html-region)
170 (error "This function requires Emacs to be compiled with libxml2"))
1518e4f0
G
171 (pop-to-buffer "*html*")
172 (erase-buffer)
173 (shr-insert-document
7b953864 174 (with-current-buffer buffer
edd9679c
LMI
175 (libxml-parse-html-region (point-min) (point-max))))
176 (goto-char (point-min)))
1518e4f0 177
6137cb82 178;;;###autoload
eadb6068
IK
179(defun shr-render-region (begin end &optional buffer)
180 "Display the HTML rendering of the region between BEGIN and END."
181 (interactive "r")
182 (unless (fboundp 'libxml-parse-html-region)
183 (error "This function requires Emacs to be compiled with libxml2"))
184 (with-current-buffer (or buffer (current-buffer))
185 (let ((dom (libxml-parse-html-region begin end)))
186 (delete-region begin end)
187 (goto-char begin)
188 (shr-insert-document dom))))
189
66627fa9
G
190;;;###autoload
191(defun shr-insert-document (dom)
9ed5a258
LI
192 "Render the parsed document DOM into the current buffer.
193DOM should be a parse tree as generated by
194`libxml-parse-html-region' or similar."
3d319c8f 195 (setq shr-content-cache nil)
9ed5a258
LI
196 (let ((start (point))
197 (shr-state nil)
bb7f5cbc 198 (shr-start nil)
dbd5ffad 199 (shr-base nil)
924d6997 200 (shr-width (or shr-width (1- (window-width)))))
9ed5a258
LI
201 (shr-descend (shr-transform-dom dom))
202 (shr-remove-trailing-whitespace start (point))))
203
204(defun shr-remove-trailing-whitespace (start end)
7c4bbb69
LI
205 (let ((width (window-width)))
206 (save-restriction
207 (narrow-to-region start end)
208 (goto-char start)
209 (while (not (eobp))
210 (end-of-line)
888ab661 211 (when (> (shr-previous-newline-padding-width (current-column)) width)
7c4bbb69
LI
212 (dolist (overlay (overlays-at (point)))
213 (when (overlay-get overlay 'before-string)
214 (overlay-put overlay 'before-string nil))))
215 (forward-line 1)))))
66627fa9
G
216
217(defun shr-copy-url ()
218 "Copy the URL under point to the kill ring.
219If called twice, then try to fetch the URL and see whether it
220redirects somewhere else."
221 (interactive)
222 (let ((url (get-text-property (point) 'shr-url)))
223 (cond
224 ((not url)
225 (message "No URL under point"))
226 ;; Resolve redirected URLs.
227 ((equal url (car kill-ring))
228 (url-retrieve
229 url
230 (lambda (a)
231 (when (and (consp a)
232 (eq (car a) :redirect))
233 (with-temp-buffer
234 (insert (cadr a))
235 (goto-char (point-min))
236 ;; Remove common tracking junk from the URL.
237 (when (re-search-forward ".utm_.*" nil t)
238 (replace-match "" t t))
239 (message "Copied %s" (buffer-string))
038b3495
LI
240 (copy-region-as-kill (point-min) (point-max)))))
241 nil t))
66627fa9
G
242 ;; Copy the URL to the kill ring.
243 (t
244 (with-temp-buffer
245 (insert url)
246 (copy-region-as-kill (point-min) (point-max))
247 (message "Copied %s" url))))))
248
7304e4dd
LMI
249(defun shr-next-link ()
250 "Skip to the next link."
251 (interactive)
be2aa135 252 (let ((skip (text-property-any (point) (point-max) 'help-echo nil)))
7304e4dd 253 (if (not (setq skip (text-property-not-all skip (point-max)
be2aa135 254 'help-echo nil)))
7304e4dd
LMI
255 (message "No next link")
256 (goto-char skip)
257 (message "%s" (get-text-property (point) 'help-echo)))))
258
259(defun shr-previous-link ()
260 "Skip to the previous link."
261 (interactive)
262 (let ((start (point))
263 (found nil))
264 ;; Skip past the current link.
265 (while (and (not (bobp))
be2aa135 266 (get-text-property (point) 'help-echo))
7304e4dd
LMI
267 (forward-char -1))
268 ;; Find the previous link.
269 (while (and (not (bobp))
be2aa135 270 (not (setq found (get-text-property (point) 'help-echo))))
7304e4dd
LMI
271 (forward-char -1))
272 (if (not found)
273 (progn
274 (message "No previous link")
275 (goto-char start))
276 ;; Put point at the start of the link.
277 (while (and (not (bobp))
be2aa135 278 (get-text-property (point) 'help-echo))
7304e4dd
LMI
279 (forward-char -1))
280 (forward-char 1)
281 (message "%s" (get-text-property (point) 'help-echo)))))
282
66627fa9
G
283(defun shr-show-alt-text ()
284 "Show the ALT text of the image under point."
285 (interactive)
286 (let ((text (get-text-property (point) 'shr-alt)))
287 (if (not text)
288 (message "No image under point")
289 (message "%s" text))))
290
2da9c605
G
291(defun shr-browse-image (&optional copy-url)
292 "Browse the image under point.
293If COPY-URL (the prefix if called interactively) is non-nil, copy
294the URL of the image to the kill buffer instead."
295 (interactive "P")
8b6f6573 296 (let ((url (get-text-property (point) 'image-url)))
2da9c605
G
297 (cond
298 ((not url)
299 (message "No image under point"))
300 (copy-url
301 (with-temp-buffer
302 (insert url)
303 (copy-region-as-kill (point-min) (point-max))
304 (message "Copied %s" url)))
305 (t
66627fa9 306 (message "Browsing %s..." url)
2da9c605 307 (browse-url url)))))
66627fa9 308
3d319c8f
LMI
309(defun shr-insert-image ()
310 "Insert the image under point into the buffer."
311 (interactive)
8b6f6573 312 (let ((url (get-text-property (point) 'image-url)))
3d319c8f
LMI
313 (if (not url)
314 (message "No image under point")
315 (message "Inserting %s..." url)
316 (url-retrieve url 'shr-image-fetched
317 (list (current-buffer) (1- (point)) (point-marker))
038b3495 318 t t))))
3d319c8f 319
89b163db
G
320(defun shr-zoom-image ()
321 "Toggle the image size.
322The size will be rotated between the default size, the original
323size, and full-buffer size."
324 (interactive)
325 (let ((url (get-text-property (point) 'image-url))
326 (size (get-text-property (point) 'image-size))
327 (buffer-read-only nil))
328 (if (not url)
329 (message "No image under point")
330 ;; Delete the old picture.
331 (while (get-text-property (point) 'image-url)
332 (forward-char -1))
333 (forward-char 1)
334 (let ((start (point)))
335 (while (get-text-property (point) 'image-url)
336 (forward-char 1))
337 (forward-char -1)
338 (put-text-property start (point) 'display nil)
339 (when (> (- (point) start) 2)
340 (delete-region start (1- (point)))))
341 (message "Inserting %s..." url)
342 (url-retrieve url 'shr-image-fetched
343 (list (current-buffer) (1- (point)) (point-marker)
344 (list (cons 'size
345 (cond ((or (eq size 'default)
346 (null size))
347 'original)
348 ((eq size 'original)
349 'full)
350 ((eq size 'full)
351 'default)))))
352 t))))
353
66627fa9
G
354;;; Utility functions.
355
870409d4
G
356(defun shr-transform-dom (dom)
357 (let ((result (list (pop dom))))
358 (dolist (arg (pop dom))
359 (push (cons (intern (concat ":" (symbol-name (car arg))) obarray)
360 (cdr arg))
361 result))
362 (dolist (sub dom)
363 (if (stringp sub)
953d41c4 364 (push (cons 'text sub) result)
870409d4
G
365 (push (shr-transform-dom sub) result)))
366 (nreverse result)))
367
dba6e3ec
LI
368(defsubst shr-generic (cont)
369 (dolist (sub cont)
370 (cond
371 ((eq (car sub) 'text)
372 (shr-insert (cdr sub)))
373 ((listp (cdr sub))
374 (shr-descend sub)))))
375
870409d4 376(defun shr-descend (dom)
2644071e
LMI
377 (let ((function
378 (or
379 ;; Allow other packages to override (or provide) rendering
380 ;; of elements.
381 (cdr (assq (car dom) shr-external-rendering-functions))
382 (intern (concat "shr-tag-" (symbol-name (car dom))) obarray)))
ebe79557 383 (style (cdr (assq :style (cdr dom))))
04db63bc 384 (shr-stylesheet shr-stylesheet)
ebe79557 385 (start (point)))
b31b26b4 386 (when style
be2aa135 387 (if (string-match "color\\|display\\|border-collapse" style)
b31b26b4
G
388 (setq shr-stylesheet (nconc (shr-parse-style style)
389 shr-stylesheet))
390 (setq style nil)))
e9145444 391 ;; If we have a display:none, then just ignore this part of the DOM.
c74cb344
G
392 (unless (equal (cdr (assq 'display shr-stylesheet)) "none")
393 (if (fboundp function)
394 (funcall function (cdr dom))
395 (shr-generic (cdr dom)))
396 (when (and shr-target-id
397 (equal (cdr (assq :id (cdr dom))) shr-target-id))
1a29adc2
LMI
398 ;; If the element was empty, we don't have anything to put the
399 ;; anchor on. So just insert a dummy character.
400 (when (= start (point))
401 (insert "*"))
c74cb344
G
402 (put-text-property start (1+ start) 'shr-target-id shr-target-id))
403 ;; If style is set, then this node has set the color.
404 (when style
405 (shr-colorize-region start (point)
406 (cdr (assq 'color shr-stylesheet))
407 (cdr (assq 'background-color shr-stylesheet)))))))
870409d4 408
ed797193
G
409(defmacro shr-char-breakable-p (char)
410 "Return non-nil if a line can be broken before and after CHAR."
411 `(aref fill-find-break-point-function-table ,char))
412(defmacro shr-char-nospace-p (char)
413 "Return non-nil if no space is required before and after CHAR."
414 `(aref fill-nospace-between-words-table ,char))
415
416;; KINSOKU is a Japanese word meaning a rule that should not be violated.
417;; In Emacs, it is a term used for characters, e.g. punctuation marks,
418;; parentheses, and so on, that should not be placed in the beginning
419;; of a line or the end of a line.
420(defmacro shr-char-kinsoku-bol-p (char)
421 "Return non-nil if a line ought not to begin with CHAR."
c6c20584
LI
422 `(let ((char ,char))
423 (and (not (eq char ?'))
424 (aref (char-category-set char) ?>))))
ed797193
G
425(defmacro shr-char-kinsoku-eol-p (char)
426 "Return non-nil if a line ought not to end with CHAR."
427 `(aref (char-category-set ,char) ?<))
428(unless (shr-char-kinsoku-bol-p (make-char 'japanese-jisx0208 33 35))
429 (load "kinsoku" nil t))
430
66627fa9 431(defun shr-insert (text)
6b7df8d3 432 (when (and (eq shr-state 'image)
89b163db 433 (not (bolp))
6b7df8d3 434 (not (string-match "\\`[ \t\n]+\\'" text)))
66627fa9
G
435 (insert "\n")
436 (setq shr-state nil))
437 (cond
438 ((eq shr-folding-mode 'none)
439 (insert text))
440 (t
c38e0c97 441 (when (and (string-match "\\`[ \t\n ]" text)
73db8b08
KY
442 (not (bolp))
443 (not (eq (char-after (1- (point))) ? )))
444 (insert " "))
c38e0c97 445 (dolist (elem (split-string text "[ \f\t\n\r\v ]+" t))
73db8b08
KY
446 (when (and (bolp)
447 (> shr-indentation 0))
448 (shr-indent))
73db8b08 449 ;; No space is needed behind a wide character categorized as
b41c2f65
KY
450 ;; kinsoku-bol, between characters both categorized as nospace,
451 ;; or at the beginning of a line.
73db8b08 452 (let (prev)
48ba8195
KY
453 (when (and (> (current-column) shr-indentation)
454 (eq (preceding-char) ? )
20438017 455 (or (= (line-beginning-position) (1- (point)))
ed797193
G
456 (and (shr-char-breakable-p
457 (setq prev (char-after (- (point) 2))))
458 (shr-char-kinsoku-bol-p prev))
459 (and (shr-char-nospace-p prev)
460 (shr-char-nospace-p (aref elem 0)))))
73db8b08 461 (delete-char -1)))
48ba8195
KY
462 ;; The shr-start is a special variable that is used to pass
463 ;; upwards the first point in the buffer where the text really
464 ;; starts.
465 (unless shr-start
466 (setq shr-start (point)))
73db8b08 467 (insert elem)
e76917e6 468 (setq shr-state nil)
e7102c0a 469 (let (found)
ac5758d1 470 (while (and (> (current-column) shr-width)
e572a7d8 471 (> shr-width 0)
ac5758d1
LI
472 (progn
473 (setq found (shr-find-fill-point))
474 (not (eolp))))
fe98a42f
KY
475 (when (eq (preceding-char) ? )
476 (delete-char -1))
477 (insert "\n")
478 (unless found
e7102c0a
KY
479 ;; No space is needed at the beginning of a line.
480 (when (eq (following-char) ? )
481 (delete-char 1)))
482 (when (> shr-indentation 0)
483 (shr-indent))
484 (end-of-line))
3d95a0f4
LMI
485 (if (<= (current-column) shr-width)
486 (insert " ")
487 ;; In case we couldn't get a valid break point (because of a
488 ;; word that's longer than `shr-width'), just break anyway.
489 (insert "\n")
490 (when (> shr-indentation 0)
491 (shr-indent)))))
c38e0c97 492 (unless (string-match "[ \t\r\n ]\\'" text)
73db8b08 493 (delete-char -1)))))
66627fa9 494
6b7df8d3 495(defun shr-find-fill-point ()
83ffd571
KY
496 (when (> (move-to-column shr-width) shr-width)
497 (backward-char 1))
ed797193
G
498 (let ((bp (point))
499 failed)
24a6b65e 500 (while (not (or (setq failed (<= (current-column) shr-indentation))
ed797193
G
501 (eq (preceding-char) ? )
502 (eq (following-char) ? )
503 (shr-char-breakable-p (preceding-char))
504 (shr-char-breakable-p (following-char))
c6c20584
LI
505 (and (shr-char-kinsoku-bol-p (preceding-char))
506 (shr-char-breakable-p (following-char))
507 (not (shr-char-kinsoku-bol-p (following-char))))
24a6b65e
LI
508 (shr-char-kinsoku-eol-p (following-char))
509 (bolp)))
83ffd571
KY
510 (backward-char 1))
511 (if failed
20438017 512 ;; There's no breakable point, so we give it up.
ed797193
G
513 (let (found)
514 (goto-char bp)
515 (unless shr-kinsoku-shorten
c6c20584
LI
516 (while (setq found (re-search-forward
517 "\\(\\c>\\)\\| \\|\\c<\\|\\c|"
518 (line-end-position) 'move)))
ac5758d1
LI
519 (if (and found
520 (not (match-beginning 1)))
ed797193 521 (goto-char (match-beginning 0)))))
b40950bf
KY
522 (or
523 (eolp)
ed797193
G
524 ;; Don't put kinsoku-bol characters at the beginning of a line,
525 ;; or kinsoku-eol characters at the end of a line.
526 (cond
527 (shr-kinsoku-shorten
528 (while (and (not (memq (preceding-char) (list ?\C-@ ?\n ? )))
529 (shr-char-kinsoku-eol-p (preceding-char)))
530 (backward-char 1))
24a6b65e 531 (when (setq failed (<= (current-column) shr-indentation))
ed797193
G
532 ;; There's no breakable point that doesn't violate kinsoku,
533 ;; so we look for the second best position.
534 (while (and (progn
535 (forward-char 1)
536 (<= (current-column) shr-width))
537 (progn
538 (setq bp (point))
539 (shr-char-kinsoku-eol-p (following-char)))))
540 (goto-char bp)))
541 ((shr-char-kinsoku-eol-p (preceding-char))
89b163db
G
542 ;; Find backward the point where kinsoku-eol characters begin.
543 (let ((count 4))
544 (while
545 (progn
546 (backward-char 1)
547 (and (> (setq count (1- count)) 0)
548 (not (memq (preceding-char) (list ?\C-@ ?\n ? )))
549 (or (shr-char-kinsoku-eol-p (preceding-char))
550 (shr-char-kinsoku-bol-p (following-char)))))))
24a6b65e 551 (when (setq failed (<= (current-column) shr-indentation))
f7f071dc
LI
552 ;; There's no breakable point that doesn't violate kinsoku,
553 ;; so we go to the second best position.
554 (if (looking-at "\\(\\c<+\\)\\c<")
555 (goto-char (match-end 1))
556 (forward-char 1))))
c6c20584 557 ((shr-char-kinsoku-bol-p (following-char))
89b163db
G
558 ;; Find forward the point where kinsoku-bol characters end.
559 (let ((count 4))
560 (while (progn
561 (forward-char 1)
562 (and (>= (setq count (1- count)) 0)
ed797193 563 (shr-char-kinsoku-bol-p (following-char))
89b163db 564 (shr-char-breakable-p (following-char))))))))
ed797193
G
565 (when (eq (following-char) ? )
566 (forward-char 1))))
567 (not failed)))
6b7df8d3 568
c74cb344
G
569(defun shr-parse-base (url)
570 ;; Always chop off anchors.
571 (when (string-match "#.*" url)
572 (setq url (substring url 0 (match-beginning 0))))
573 (let* ((parsed (url-generic-parse-url url))
574 (local (url-filename parsed)))
575 (setf (url-filename parsed) "")
576 ;; Chop off the bit after the last slash.
577 (when (string-match "\\`\\(.*/\\)[^/]+\\'" local)
578 (setq local (match-string 1 local)))
579 ;; Always make the local bit end with a slash.
580 (when (and (not (zerop (length local)))
581 (not (eq (aref local (1- (length local))) ?/)))
582 (setq local (concat local "/")))
583 (list (url-recreate-url parsed)
584 local
585 (url-type parsed)
586 url)))
587
588(defun shr-expand-url (url &optional base)
589 (setq base
590 (if base
591 (shr-parse-base base)
592 ;; Bound by the parser.
593 shr-base))
594 (when (zerop (length url))
595 (setq url nil))
596 (cond ((or (not url)
597 (not base)
598 (string-match "\\`[a-z]*:" url))
599 ;; Absolute URL.
600 (or url (car base)))
601 ((eq (aref url 0) ?/)
602 (if (and (> (length url) 1)
603 (eq (aref url 1) ?/))
604 ;; //host...; just use the protocol
605 (concat (nth 2 base) ":" url)
606 ;; Just use the host name part.
607 (concat (car base) url)))
608 ((eq (aref url 0) ?#)
609 ;; A link to an anchor.
610 (concat (nth 3 base) url))
611 (t
612 ;; Totally relative.
f596f897 613 (concat (car base) (expand-file-name url (cadr base))))))
dbd5ffad 614
66627fa9
G
615(defun shr-ensure-newline ()
616 (unless (zerop (current-column))
617 (insert "\n")))
a41c2e6d
G
618
619(defun shr-ensure-paragraph ()
620 (unless (bobp)
f7aa248a 621 (if (<= (current-column) shr-indentation)
71e691a5
G
622 (unless (save-excursion
623 (forward-line -1)
624 (looking-at " *$"))
a41c2e6d
G
625 (insert "\n"))
626 (if (save-excursion
627 (beginning-of-line)
be2aa135
LMI
628 ;; If the current line is totally blank, and doesn't even
629 ;; have any face properties set, then delete the blank
630 ;; space.
631 (and (looking-at " *$")
632 (not (get-text-property (point) 'face))
633 (not (= (next-single-property-change (point) 'face nil
634 (line-end-position))
635 (line-end-position)))))
89b163db 636 (delete-region (match-beginning 0) (match-end 0))
a41c2e6d
G
637 (insert "\n\n")))))
638
66627fa9 639(defun shr-indent ()
f7aa248a
G
640 (when (> shr-indentation 0)
641 (insert (make-string shr-indentation ? ))))
870409d4 642
a41c2e6d 643(defun shr-fontize-cont (cont &rest types)
870409d4
G
644 (let (shr-start)
645 (shr-generic cont)
a41c2e6d
G
646 (dolist (type types)
647 (shr-add-font (or shr-start (point)) (point) type))))
870409d4 648
7304e4dd
LMI
649;; Add face to the region, but avoid putting the font properties on
650;; blank text at the start of the line, and the newline at the end, to
651;; avoid ugliness.
870409d4 652(defun shr-add-font (start end type)
be2aa135
LMI
653 (unless shr-inhibit-decoration
654 (save-excursion
655 (goto-char start)
656 (while (< (point) end)
657 (when (bolp)
658 (skip-chars-forward " "))
659 (add-face-text-property (point) (min (line-end-position) end) type t)
660 (if (< (line-end-position) end)
661 (forward-line 1)
662 (goto-char end))))))
870409d4 663
8ba8eec5
LMI
664(defun shr-mouse-browse-url (ev)
665 "Browse the URL under the mouse cursor."
666 (interactive "e")
667 (mouse-set-point ev)
668 (shr-browse-url))
669
1211de50 670(defun shr-browse-url (&optional external mouse-event)
bdaa086b
LMI
671 "Browse the URL under point.
672If EXTERNAL, browse the URL using `shr-external-browser'."
1211de50
LMI
673 (interactive (list current-prefix-arg last-nonmenu-event))
674 (mouse-set-point mouse-event)
71e691a5 675 (let ((url (get-text-property (point) 'shr-url)))
181cb5fb
G
676 (cond
677 ((not url)
678 (message "No link under point"))
679 ((string-match "^mailto:" url)
39ddff39 680 (browse-url-mail url))
181cb5fb 681 (t
bdaa086b
LMI
682 (if external
683 (funcall shr-external-browser url)
684 (browse-url url))))))
71e691a5 685
cdf1fca4
LMI
686(defun shr-save-contents (directory)
687 "Save the contents from URL in a file."
688 (interactive "DSave contents of URL to directory: ")
689 (let ((url (get-text-property (point) 'shr-url)))
690 (if (not url)
691 (message "No link under point")
692 (url-retrieve (shr-encode-url url)
038b3495
LI
693 'shr-store-contents (list url directory)
694 nil t))))
cdf1fca4
LMI
695
696(defun shr-store-contents (status url directory)
697 (unless (plist-get status :error)
698 (when (or (search-forward "\n\n" nil t)
699 (search-forward "\r\n\r\n" nil t))
700 (write-region (point) (point-max)
701 (expand-file-name (file-name-nondirectory url)
702 directory)))))
703
89b163db 704(defun shr-image-fetched (status buffer start end &optional flags)
0e2cebe5
LI
705 (let ((image-buffer (current-buffer)))
706 (when (and (buffer-name buffer)
707 (not (plist-get status :error)))
708 (url-store-in-cache image-buffer)
709 (when (or (search-forward "\n\n" nil t)
710 (search-forward "\r\n\r\n" nil t))
21c58ae2 711 (let ((data (shr-parse-image-data)))
0e2cebe5
LI
712 (with-current-buffer buffer
713 (save-excursion
714 (let ((alt (buffer-substring start end))
89b163db 715 (properties (text-properties-at start))
0e2cebe5
LI
716 (inhibit-read-only t))
717 (delete-region start end)
718 (goto-char start)
89b163db
G
719 (funcall shr-put-image-function data alt flags)
720 (while properties
721 (let ((type (pop properties))
722 (value (pop properties)))
723 (unless (memq type '(display image-size))
724 (put-text-property start (point) type value))))))))))
0e2cebe5 725 (kill-buffer image-buffer)))
870409d4 726
2250b351
DE
727(defun shr-image-from-data (data)
728 "Return an image from the data: URI content DATA."
729 (when (string-match
730 "\\(\\([^/;,]+\\(/[^;,]+\\)?\\)\\(;[^;,]+\\)*\\)?,\\(.*\\)"
731 data)
732 (let ((param (match-string 4 data))
733 (payload (url-unhex-string (match-string 5 data))))
734 (when (string-match "^.*\\(;[ \t]*base64\\)$" param)
735 (setq payload (base64-decode-string payload)))
736 payload)))
737
986ed135
GM
738;; Behind display-graphic-p test.
739(declare-function image-size "image.c" (spec &optional pixels frame))
740(declare-function image-animate "image" (image &optional index limit))
741
21c58ae2
LMI
742(defun shr-put-image (spec alt &optional flags)
743 "Insert image SPEC with a string ALT. Return image.
744SPEC is either an image data blob, or a list where the first
745element is the data blob and the second element is the content-type."
4abff904 746 (if (display-graphic-p)
89b163db 747 (let* ((size (cdr (assq 'size flags)))
21c58ae2
LMI
748 (data (if (consp spec)
749 (car spec)
750 spec))
751 (content-type (and (consp spec)
752 (cadr spec)))
89b163db
G
753 (start (point))
754 (image (cond
755 ((eq size 'original)
21c58ae2 756 (create-image data nil t :ascent 100
c6b7ccaa 757 :format content-type))
89b163db
G
758 ((eq size 'full)
759 (ignore-errors
e9145444 760 (shr-rescale-image data content-type)))
89b163db
G
761 (t
762 (ignore-errors
e9145444 763 (shr-rescale-image data content-type))))))
4abff904 764 (when image
c0f9edce
G
765 ;; When inserting big-ish pictures, put them at the
766 ;; beginning of the line.
767 (when (and (> (current-column) 0)
768 (> (car (image-size image t)) 400))
769 (insert "\n"))
89b163db 770 (if (eq size 'original)
7304e4dd 771 (insert-sliced-image image (or alt "*") nil 20 1)
89b163db
G
772 (insert-image image (or alt "*")))
773 (put-text-property start (point) 'image-size size)
8a73034e
IK
774 (when (and shr-image-animate
775 (cond ((fboundp 'image-multi-frame-p)
dd8620de
GM
776 ;; Only animate multi-frame things that specify a
777 ;; delay; eg animated gifs as opposed to
778 ;; multi-page tiffs. FIXME?
8a73034e
IK
779 (cdr (image-multi-frame-p image)))
780 ((fboundp 'image-animated-p)
781 (image-animated-p image))))
782 (image-animate image nil 60)))
b9bdaf74 783 image)
99e65b2d 784 (insert alt)))
870409d4 785
e9145444
SM
786(defun shr-rescale-image (data &optional content-type)
787 "Rescale DATA, if too big, to fit the current buffer."
788 (if (not (and (fboundp 'imagemagick-types)
789 (get-buffer-window (current-buffer))))
f3f9606c
LMI
790 (create-image data nil t :ascent 100)
791 (let ((edges (window-inside-pixel-edges
792 (get-buffer-window (current-buffer)))))
793 (create-image
794 data 'imagemagick t
795 :ascent 100
796 :max-width (truncate (* shr-max-image-proportion
797 (- (nth 2 edges) (nth 0 edges))))
798 :max-height (truncate (* shr-max-image-proportion
21c58ae2 799 (- (nth 3 edges) (nth 1 edges))))
c6b7ccaa 800 :format content-type))))
870409d4 801
85a45a69
GM
802;; url-cache-extract autoloads url-cache.
803(declare-function url-cache-create-filename "url-cache" (url))
804(autoload 'mm-disable-multibyte "mm-util")
39ddff39 805(autoload 'browse-url-mail "browse-url")
85a45a69 806
870409d4
G
807(defun shr-get-image-data (url)
808 "Get image data for URL.
809Return a string with image data."
810 (with-temp-buffer
811 (mm-disable-multibyte)
71e691a5 812 (when (ignore-errors
ab67634f 813 (url-cache-extract (url-cache-create-filename (shr-encode-url url)))
71e691a5
G
814 t)
815 (when (or (search-forward "\n\n" nil t)
816 (search-forward "\r\n\r\n" nil t))
21c58ae2
LMI
817 (shr-parse-image-data)))))
818
819(defun shr-parse-image-data ()
820 (list
821 (buffer-substring (point) (point-max))
822 (save-excursion
823 (save-restriction
824 (narrow-to-region (point-min) (point))
825 (let ((content-type (mail-fetch-field "content-type")))
826 (and content-type
827 (intern content-type obarray)))))))
870409d4 828
40de2c6d
KY
829(defun shr-image-displayer (content-function)
830 "Return a function to display an image.
831CONTENT-FUNCTION is a function to retrieve an image for a cid url that
832is an argument. The function to be returned takes three arguments URL,
53964682 833START, and END. Note that START and END should be markers."
40de2c6d 834 `(lambda (url start end)
f8d8a97b
KY
835 (when url
836 (if (string-match "\\`cid:" url)
837 ,(when content-function
838 `(let ((image (funcall ,content-function
839 (substring url (match-end 0)))))
840 (when image
841 (goto-char start)
b9bdaf74 842 (funcall shr-put-image-function
195b2593 843 image (buffer-substring start end))
e2d0ba98 844 (delete-region (point) end))))
f8d8a97b
KY
845 (url-retrieve url 'shr-image-fetched
846 (list (current-buffer) start end)
038b3495 847 t t)))))
40de2c6d 848
66627fa9
G
849(defun shr-heading (cont &rest types)
850 (shr-ensure-paragraph)
851 (apply #'shr-fontize-cont cont types)
852 (shr-ensure-paragraph))
853
04db63bc 854(defun shr-urlify (start url &optional title)
ca3cf0a5 855 (shr-add-font start (point) 'shr-link)
7304e4dd
LMI
856 (add-text-properties
857 start (point)
858 (list 'shr-url url
be2aa135 859 'help-echo (if title (format "%s (%s)" url title) url)
d50fceab 860 'follow-link t
5a2a7735 861 'mouse-face 'highlight
970ad972 862 'keymap shr-map)))
de635afe
G
863
864(defun shr-encode-url (url)
865 "Encode URL."
866 (browse-url-url-encode-chars url "[)$ ]"))
867
ebe79557
LMI
868(autoload 'shr-color-visible "shr-color")
869(autoload 'shr-color->hexadecimal "shr-color")
144b7b5c
G
870
871(defun shr-color-check (fg bg)
872 "Check that FG is visible on BG.
873Returns (fg bg) with corrected values.
874Returns nil if the colors that would be used are the default
875ones, in case fg and bg are nil."
876 (when (or fg bg)
877 (let ((fixed (cond ((null fg) 'fg)
878 ((null bg) 'bg))))
879 ;; Convert colors to hexadecimal, or set them to default.
880 (let ((fg (or (shr-color->hexadecimal fg)
881 (frame-parameter nil 'foreground-color)))
882 (bg (or (shr-color->hexadecimal bg)
883 (frame-parameter nil 'background-color))))
884 (cond ((eq fixed 'bg)
885 ;; Only return the new fg
886 (list nil (cadr (shr-color-visible bg fg t))))
887 ((eq fixed 'fg)
888 ;; Invert args and results and return only the new bg
889 (list (cadr (shr-color-visible fg bg t)) nil))
890 (t
891 (shr-color-visible bg fg)))))))
892
04db63bc 893(defun shr-colorize-region (start end fg &optional bg)
be2aa135
LMI
894 (when (and (not shr-inhibit-decoration)
895 (or fg bg))
04db63bc 896 (let ((new-colors (shr-color-check fg bg)))
144b7b5c 897 (when new-colors
60568d74 898 (when fg
be2aa135
LMI
899 (add-face-text-property start end
900 (list :foreground (cadr new-colors))
901 t))
04db63bc 902 (when bg
be2aa135
LMI
903 (add-face-text-property start end
904 (list :background (car new-colors))
905 t)))
ec72bf63 906 new-colors)))
04db63bc 907
c5ecc769
G
908(defun shr-expand-newlines (start end color)
909 (save-restriction
d709b79a
LI
910 ;; Skip past all white space at the start and ends.
911 (goto-char start)
912 (skip-chars-forward " \t\n")
913 (beginning-of-line)
914 (setq start (point))
915 (goto-char end)
916 (skip-chars-backward " \t\n")
917 (forward-line 1)
918 (setq end (point))
c5ecc769 919 (narrow-to-region start end)
160ae063 920 (let ((width (shr-buffer-width))
c5ecc769
G
921 column)
922 (goto-char (point-min))
923 (while (not (eobp))
924 (end-of-line)
19e0dbe0
KY
925 (when (and (< (setq column (current-column)) width)
926 (< (setq column (shr-previous-newline-padding-width column))
d709b79a 927 width))
7304e4dd 928 (let ((overlay (make-overlay (point) (1+ (point)))))
c5ecc769 929 (overlay-put overlay 'before-string
d709b79a
LI
930 (concat
931 (mapconcat
932 (lambda (overlay)
14596870
KY
933 (let ((string (plist-get
934 (overlay-properties overlay)
935 'before-string)))
d709b79a
LI
936 (if (not string)
937 ""
938 (overlay-put overlay 'before-string "")
939 string)))
940 (overlays-at (point))
941 "")
19e0dbe0 942 (propertize (make-string (- width column) ? )
d709b79a 943 'face (list :background color))))))
c5ecc769 944 (forward-line 1)))))
04db63bc 945
d709b79a
LI
946(defun shr-previous-newline-padding-width (width)
947 (let ((overlays (overlays-at (point)))
948 (previous-width 0))
949 (if (null overlays)
950 width
951 (dolist (overlay overlays)
952 (setq previous-width
953 (+ previous-width
14596870
KY
954 (length (plist-get (overlay-properties overlay)
955 'before-string)))))
d709b79a
LI
956 (+ width previous-width))))
957
66627fa9
G
958;;; Tag-specific rendering rules.
959
144b7b5c 960(defun shr-tag-body (cont)
04db63bc 961 (let* ((start (point))
af4e5f4c
G
962 (fgcolor (cdr (or (assq :fgcolor cont)
963 (assq :text cont))))
04db63bc 964 (bgcolor (cdr (assq :bgcolor cont)))
b31b26b4
G
965 (shr-stylesheet (list (cons 'color fgcolor)
966 (cons 'background-color bgcolor))))
144b7b5c 967 (shr-generic cont)
04db63bc 968 (shr-colorize-region start (point) fgcolor bgcolor)))
144b7b5c 969
e9145444 970(defun shr-tag-style (_cont)
b31b26b4
G
971 )
972
e9145444 973(defun shr-tag-script (_cont)
f73341e2
LMI
974 )
975
e9145444 976(defun shr-tag-comment (_cont)
fb1b0ef6
LMI
977 )
978
c74cb344
G
979(defun shr-dom-to-xml (dom)
980 "Convert DOM into a string containing the xml representation."
981 (let ((arg " ")
cc477daa
LI
982 (text "")
983 url)
c74cb344
G
984 (dolist (sub (cdr dom))
985 (cond
986 ((listp (cdr sub))
cc477daa
LI
987 ;; Ignore external image definitions if required.
988 ;; <image xlink:href="http://TRACKING_URL/"/>
989 (when (or (not (eq (car sub) 'image))
990 (not (setq url (cdr (assq ':xlink:href (cdr sub)))))
991 (not shr-blocked-images)
992 (not (string-match shr-blocked-images url)))
993 (setq text (concat text (shr-dom-to-xml sub)))))
c74cb344
G
994 ((eq (car sub) 'text)
995 (setq text (concat text (cdr sub))))
996 (t
997 (setq arg (concat arg (format "%s=\"%s\" "
998 (substring (symbol-name (car sub)) 1)
999 (cdr sub)))))))
1000 (format "<%s%s>%s</%s>"
1001 (car dom)
1002 (substring arg 0 (1- (length arg)))
1003 text
1004 (car dom))))
1005
65e704b9 1006(defun shr-tag-svg (cont)
cc477daa
LI
1007 (when (and (image-type-available-p 'svg)
1008 (not shr-inhibit-images))
c74cb344
G
1009 (funcall shr-put-image-function
1010 (shr-dom-to-xml (cons 'svg cont))
1011 "SVG Image")))
65e704b9 1012
a3af2929
LMI
1013(defun shr-tag-sup (cont)
1014 (let ((start (point)))
1015 (shr-generic cont)
1016 (put-text-property start (point) 'display '(raise 0.5))))
1017
1018(defun shr-tag-sub (cont)
1019 (let ((start (point)))
1020 (shr-generic cont)
1021 (put-text-property start (point) 'display '(raise -0.5))))
1022
7bafe9bc
LMI
1023(defun shr-tag-label (cont)
1024 (shr-generic cont)
1025 (shr-ensure-paragraph))
1026
66627fa9
G
1027(defun shr-tag-p (cont)
1028 (shr-ensure-paragraph)
f7aa248a 1029 (shr-indent)
66627fa9
G
1030 (shr-generic cont)
1031 (shr-ensure-paragraph))
1032
036d93bc
KY
1033(defun shr-tag-div (cont)
1034 (shr-ensure-newline)
1035 (shr-indent)
1036 (shr-generic cont)
1037 (shr-ensure-newline))
1038
6eee2678
LMI
1039(defun shr-tag-s (cont)
1040 (shr-fontize-cont cont 'shr-strike-through))
1041
55385ebc
JD
1042(defun shr-tag-del (cont)
1043 (shr-fontize-cont cont 'shr-strike-through))
1044
66627fa9
G
1045(defun shr-tag-b (cont)
1046 (shr-fontize-cont cont 'bold))
1047
1048(defun shr-tag-i (cont)
1049 (shr-fontize-cont cont 'italic))
1050
1051(defun shr-tag-em (cont)
087d8265 1052 (shr-fontize-cont cont 'italic))
66627fa9 1053
530f7b67
LMI
1054(defun shr-tag-strong (cont)
1055 (shr-fontize-cont cont 'bold))
1056
66627fa9
G
1057(defun shr-tag-u (cont)
1058 (shr-fontize-cont cont 'underline))
1059
2e76c12c
LMI
1060(defun shr-parse-style (style)
1061 (when style
a2994808
JD
1062 (save-match-data
1063 (when (string-match "\n" style)
1064 (setq style (replace-match " " t t style))))
2e76c12c
LMI
1065 (let ((plist nil))
1066 (dolist (elem (split-string style ";"))
1067 (when elem
1068 (setq elem (split-string elem ":"))
1069 (when (and (car elem)
1070 (cadr elem))
1071 (let ((name (replace-regexp-in-string "^ +\\| +$" "" (car elem)))
1072 (value (replace-regexp-in-string "^ +\\| +$" "" (cadr elem))))
144b7b5c
G
1073 (when (string-match " *!important\\'" value)
1074 (setq value (substring value 0 (match-beginning 0))))
2e76c12c
LMI
1075 (push (cons (intern name obarray)
1076 value)
1077 plist)))))
1078 plist)))
1079
dbd5ffad 1080(defun shr-tag-base (cont)
be2aa135
LMI
1081 (let ((base (cdr (assq :href cont))))
1082 (when base
1083 (setq shr-base (shr-parse-base base))))
266c63b5 1084 (shr-generic cont))
dbd5ffad 1085
66627fa9
G
1086(defun shr-tag-a (cont)
1087 (let ((url (cdr (assq :href cont)))
04db63bc 1088 (title (cdr (assq :title cont)))
66627fa9
G
1089 (start (point))
1090 shr-start)
1091 (shr-generic cont)
1c4b1e61 1092 (when (and shr-target-id
e9145444 1093 (equal (cdr (assq :name cont)) shr-target-id))
1c4b1e61
LMI
1094 ;; We have a zero-length <a name="foo"> element, so just
1095 ;; insert... something.
1096 (when (= start (point))
1097 (shr-ensure-newline)
1098 (insert " "))
1099 (put-text-property start (1+ start) 'shr-target-id shr-target-id))
be2aa135
LMI
1100 (when (and url
1101 (not shr-inhibit-decoration))
cc21c235 1102 (shr-urlify (or shr-start start) (shr-expand-url url) title))))
de635afe
G
1103
1104(defun shr-tag-object (cont)
99e65b2d
G
1105 (let ((start (point))
1106 url)
1107 (dolist (elem cont)
1108 (when (eq (car elem) 'embed)
1109 (setq url (or url (cdr (assq :src (cdr elem))))))
1110 (when (and (eq (car elem) 'param)
1111 (equal (cdr (assq :name (cdr elem))) "movie"))
1112 (setq url (or url (cdr (assq :value (cdr elem)))))))
de635afe
G
1113 (when url
1114 (shr-insert " [multimedia] ")
dbd5ffad 1115 (shr-urlify start (shr-expand-url url)))
99e65b2d
G
1116 (shr-generic cont)))
1117
ad9a773c
RS
1118(defcustom shr-prefer-media-type-alist '(("webm" . 1.0)
1119 ("ogv" . 1.0)
1120 ("ogg" . 1.0)
1121 ("opus" . 1.0)
1122 ("flac" . 0.9)
1123 ("wav" . 0.5))
1124 "Preferences for media types.
1125The key element should be a regexp matched against the type of the source or
1126url if no type is specified. The value should be a float in the range 0.0 to
11271.0. Media elements with higher value are preferred."
1128 :version "24.4"
1129 :group 'shr
1130 :type '(alist :key-type regexp :value-type float))
1131
1132(defun shr--get-media-pref (elem)
1133 "Determine the preference for ELEM.
1134The preference is a float determined from `shr-prefer-media-type'."
1135 (let ((type (cdr (assq :type elem)))
1136 (p 0.0))
1137 (unless type
1138 (setq type (cdr (assq :src elem))))
1139 (when type
1140 (dolist (pref shr-prefer-media-type-alist)
1141 (when (and
1142 (> (cdr pref) p)
1143 (string-match-p (car pref) type))
1144 (setq p (cdr pref)))))
1145 p))
1146
1147(defun shr--extract-best-source (cont &optional url pref)
1148 "Extract the best `:src' property from <source> blocks in CONT."
1149 (setq pref (or pref -1.0))
1150 (let (new-pref)
1151 (dolist (elem cont)
073d5cfd
LMI
1152 (when (and (eq (car elem) 'source)
1153 (< pref
1154 (setq new-pref
1155 (shr--get-media-pref elem))))
1156 (setq pref new-pref
1157 url (cdr (assq :src elem)))
f224e500 1158 ;; libxml's html parser isn't HTML5 compliant and non terminated
ad9a773c
RS
1159 ;; source tags might end up as children. So recursion it is...
1160 (dolist (child (cdr elem))
073d5cfd 1161 (when (eq (car child) 'source)
ad9a773c
RS
1162 (let ((ret (shr--extract-best-source (list child) url pref)))
1163 (when (< pref (cdr ret))
1164 (setq url (car ret)
1165 pref (cdr ret)))))))))
1166 (cons url pref))
1167
99e65b2d
G
1168(defun shr-tag-video (cont)
1169 (let ((image (cdr (assq :poster cont)))
ad9a773c
RS
1170 (url (cdr (assq :src cont)))
1171 (start (point)))
1172 (unless url
1173 (setq url (car (shr--extract-best-source cont))))
177948a5
RS
1174 (if image
1175 (shr-tag-img nil image)
1176 (shr-insert " [video] "))
dbd5ffad 1177 (shr-urlify start (shr-expand-url url))))
ab67634f 1178
eb2dd24d
RS
1179(defun shr-tag-audio (cont)
1180 (let ((url (cdr (assq :src cont)))
1181 (start (point)))
ad9a773c
RS
1182 (unless url
1183 (setq url (car (shr--extract-best-source cont))))
eb2dd24d
RS
1184 (shr-insert " [audio] ")
1185 (shr-urlify start (shr-expand-url url))))
1186
99e65b2d
G
1187(defun shr-tag-img (cont &optional url)
1188 (when (or url
1189 (and cont
e330b646 1190 (> (length (cdr (assq :src cont))) 0)))
68f6bd17
KY
1191 (when (and (> (current-column) 0)
1192 (not (eq shr-state 'image)))
1193 (insert "\n"))
1194 (let ((alt (cdr (assq :alt cont)))
dbd5ffad 1195 (url (shr-expand-url (or url (cdr (assq :src cont))))))
68f6bd17
KY
1196 (let ((start (point-marker)))
1197 (when (zerop (length alt))
953d41c4 1198 (setq alt "*"))
68f6bd17 1199 (cond
99e65b2d
G
1200 ((or (member (cdr (assq :height cont)) '("0" "1"))
1201 (member (cdr (assq :width cont)) '("0" "1")))
1202 ;; Ignore zero-sized or single-pixel images.
1203 )
2250b351
DE
1204 ((and (not shr-inhibit-images)
1205 (string-match "\\`data:" url))
1206 (let ((image (shr-image-from-data (substring url (match-end 0)))))
1207 (if image
1208 (funcall shr-put-image-function image alt)
1209 (insert alt))))
68f6bd17
KY
1210 ((and (not shr-inhibit-images)
1211 (string-match "\\`cid:" url))
1212 (let ((url (substring url (match-end 0)))
1213 image)
1214 (if (or (not shr-content-function)
1215 (not (setq image (funcall shr-content-function url))))
1216 (insert alt)
b9bdaf74 1217 (funcall shr-put-image-function image alt))))
68f6bd17
KY
1218 ((or shr-inhibit-images
1219 (and shr-blocked-images
1220 (string-match shr-blocked-images url)))
1221 (setq shr-start (point))
1222 (let ((shr-state 'space))
b354bc53
KY
1223 (if (> (string-width alt) 8)
1224 (shr-insert (truncate-string-to-width alt 8))
68f6bd17 1225 (shr-insert alt))))
728518c3
LMI
1226 ((and (not shr-ignore-cache)
1227 (url-is-cached (shr-encode-url url)))
b9bdaf74 1228 (funcall shr-put-image-function (shr-get-image-data url) alt))
68f6bd17 1229 (t
64522086 1230 (insert alt " ")
728518c3
LMI
1231 (when (and shr-ignore-cache
1232 (url-is-cached (shr-encode-url url)))
1233 (let ((file (url-cache-create-filename (shr-encode-url url))))
1234 (when (file-exists-p file)
1235 (delete-file file))))
038b3495 1236 (url-queue-retrieve
f3b146e9 1237 (shr-encode-url url) 'shr-image-fetched
64522086 1238 (list (current-buffer) start (set-marker (make-marker) (1- (point))))
038b3495 1239 t t)))
a959fc40
KY
1240 (when (zerop shr-table-depth) ;; We are not in a table.
1241 (put-text-property start (point) 'keymap shr-map)
1242 (put-text-property start (point) 'shr-alt alt)
1243 (put-text-property start (point) 'image-url url)
1244 (put-text-property start (point) 'image-displayer
1245 (shr-image-displayer shr-content-function))
35408b1a
AS
1246 (put-text-property start (point) 'help-echo
1247 (or (cdr (assq :title cont))
1248 alt)))
68f6bd17 1249 (setq shr-state 'image)))))
66627fa9
G
1250
1251(defun shr-tag-pre (cont)
1252 (let ((shr-folding-mode 'none))
1253 (shr-ensure-newline)
f7aa248a 1254 (shr-indent)
66627fa9
G
1255 (shr-generic cont)
1256 (shr-ensure-newline)))
1257
1258(defun shr-tag-blockquote (cont)
1259 (shr-ensure-paragraph)
f7aa248a 1260 (shr-indent)
66627fa9
G
1261 (let ((shr-indentation (+ shr-indentation 4)))
1262 (shr-generic cont))
1263 (shr-ensure-paragraph))
a41c2e6d 1264
d2aa9780
LMI
1265(defun shr-tag-dl (cont)
1266 (shr-ensure-paragraph)
1267 (shr-generic cont)
1268 (shr-ensure-paragraph))
1269
1270(defun shr-tag-dt (cont)
1271 (shr-ensure-newline)
1272 (shr-generic cont)
1273 (shr-ensure-newline))
1274
1275(defun shr-tag-dd (cont)
1276 (shr-ensure-newline)
1277 (let ((shr-indentation (+ shr-indentation 4)))
1278 (shr-generic cont)))
1279
a41c2e6d
G
1280(defun shr-tag-ul (cont)
1281 (shr-ensure-paragraph)
1282 (let ((shr-list-mode 'ul))
3d319c8f
LMI
1283 (shr-generic cont))
1284 (shr-ensure-paragraph))
a41c2e6d
G
1285
1286(defun shr-tag-ol (cont)
3d319c8f 1287 (shr-ensure-paragraph)
a41c2e6d 1288 (let ((shr-list-mode 1))
3d319c8f
LMI
1289 (shr-generic cont))
1290 (shr-ensure-paragraph))
a41c2e6d
G
1291
1292(defun shr-tag-li (cont)
c74cb344 1293 (shr-ensure-newline)
f7aa248a 1294 (shr-indent)
8028ed5c
LMI
1295 (let* ((bullet
1296 (if (numberp shr-list-mode)
1297 (prog1
1298 (format "%d " shr-list-mode)
1299 (setq shr-list-mode (1+ shr-list-mode)))
c74cb344 1300 shr-bullet))
8028ed5c
LMI
1301 (shr-indentation (+ shr-indentation (length bullet))))
1302 (insert bullet)
1303 (shr-generic cont)))
a41c2e6d
G
1304
1305(defun shr-tag-br (cont)
89b163db
G
1306 (when (and (not (bobp))
1307 ;; Only add a newline if we break the current line, or
1308 ;; the previous line isn't a blank line.
1309 (or (not (bolp))
1310 (and (> (- (point) 2) (point-min))
1311 (not (= (char-after (- (point) 2)) ?\n)))))
f7aa248a
G
1312 (insert "\n")
1313 (shr-indent))
a41c2e6d
G
1314 (shr-generic cont))
1315
308c9d24 1316(defun shr-tag-span (cont)
be2aa135 1317 (shr-generic cont))
308c9d24 1318
a41c2e6d 1319(defun shr-tag-h1 (cont)
e4dbdb09 1320 (shr-heading cont 'bold 'underline))
a41c2e6d
G
1321
1322(defun shr-tag-h2 (cont)
e4dbdb09 1323 (shr-heading cont 'bold))
a41c2e6d
G
1324
1325(defun shr-tag-h3 (cont)
e4dbdb09 1326 (shr-heading cont 'italic))
a41c2e6d
G
1327
1328(defun shr-tag-h4 (cont)
e4dbdb09 1329 (shr-heading cont))
a41c2e6d
G
1330
1331(defun shr-tag-h5 (cont)
e4dbdb09 1332 (shr-heading cont))
a41c2e6d
G
1333
1334(defun shr-tag-h6 (cont)
e4dbdb09 1335 (shr-heading cont))
a41c2e6d 1336
e9145444 1337(defun shr-tag-hr (_cont)
3d319c8f 1338 (shr-ensure-newline)
6b7df8d3 1339 (insert (make-string shr-width shr-hr-line) "\n"))
3d319c8f 1340
144b7b5c
G
1341(defun shr-tag-title (cont)
1342 (shr-heading cont 'bold 'underline))
1343
1110d53b 1344(defun shr-tag-font (cont)
b31b26b4
G
1345 (let* ((start (point))
1346 (color (cdr (assq :color cont)))
1347 (shr-stylesheet (nconc (list (cons 'color color))
1348 shr-stylesheet)))
1110d53b 1349 (shr-generic cont)
b31b26b4
G
1350 (when color
1351 (shr-colorize-region start (point) color
1352 (cdr (assq 'background-color shr-stylesheet))))))
1110d53b 1353
66627fa9 1354;;; Table rendering algorithm.
a41c2e6d 1355
a0ec382a
LMI
1356;; Table rendering is the only complicated thing here. We do this by
1357;; first counting how many TDs there are in each TR, and registering
1358;; how wide they think they should be ("width=45%", etc). Then we
1359;; render each TD separately (this is done in temporary buffers, so
1360;; that we can use all the rendering machinery as if we were in the
1361;; main buffer). Now we know how much space each TD really takes, so
1362;; we then render everything again with the new widths, and finally
1363;; insert all these boxes into the main buffer.
6c769311 1364(defun shr-tag-table-1 (cont)
71e691a5
G
1365 (setq cont (or (cdr (assq 'tbody cont))
1366 cont))
130e977f 1367 (let* ((shr-inhibit-images t)
99e65b2d 1368 (shr-table-depth (1+ shr-table-depth))
83ffd571 1369 (shr-kinsoku-shorten t)
a0ec382a 1370 ;; Find all suggested widths.
130e977f 1371 (columns (shr-column-specs cont))
a0ec382a 1372 ;; Compute how many characters wide each TD should be.
71e691a5 1373 (suggested-widths (shr-pro-rate-columns columns))
a0ec382a
LMI
1374 ;; Do a "test rendering" to see how big each TD is (this can
1375 ;; be smaller (if there's little text) or bigger (if there's
1376 ;; unbreakable text).
71e691a5 1377 (sketch (shr-make-table cont suggested-widths))
160ae063
LMI
1378 ;; Compute the "natural" width by setting each column to 500
1379 ;; characters and see how wide they really render.
1380 (natural (shr-make-table cont (make-vector (length columns) 500)))
1381 (sketch-widths (shr-table-widths sketch natural suggested-widths)))
030158f3 1382 ;; This probably won't work very well.
83ffd571
KY
1383 (when (> (+ (loop for width across sketch-widths
1384 summing (1+ width))
1385 shr-indentation 1)
030158f3
G
1386 (frame-width))
1387 (setq truncate-lines t))
a0ec382a 1388 ;; Then render the table again with these new "hard" widths.
f462d10a 1389 (shr-insert-table (shr-make-table cont sketch-widths t) sketch-widths)))
130e977f 1390
6c769311
KY
1391(defun shr-tag-table (cont)
1392 (shr-ensure-paragraph)
1393 (let* ((caption (cdr (assq 'caption cont)))
1394 (header (cdr (assq 'thead cont)))
1395 (body (or (cdr (assq 'tbody cont)) cont))
1396 (footer (cdr (assq 'tfoot cont)))
144b7b5c 1397 (bgcolor (cdr (assq :bgcolor cont)))
60568d74
LMI
1398 (start (point))
1399 (shr-stylesheet (nconc (list (cons 'background-color bgcolor))
1400 shr-stylesheet))
6c769311
KY
1401 (nheader (if header (shr-max-columns header)))
1402 (nbody (if body (shr-max-columns body)))
1403 (nfooter (if footer (shr-max-columns footer))))
2146e256
LMI
1404 (if (and (not caption)
1405 (not header)
1406 (not (cdr (assq 'tbody cont)))
1407 (not (cdr (assq 'tr cont)))
1408 (not footer))
1409 ;; The table is totally invalid and just contains random junk.
1410 ;; Try to output it anyway.
1411 (shr-generic cont)
1412 ;; It's a real table, so render it.
1413 (shr-tag-table-1
1414 (nconc
1415 (if caption `((tr (td ,@caption))))
1416 (if header
1417 (if footer
8feacce0 1418 ;; header + body + footer
2146e256
LMI
1419 (if (= nheader nbody)
1420 (if (= nbody nfooter)
1421 `((tr (td (table (tbody ,@header ,@body ,@footer)))))
1422 (nconc `((tr (td (table (tbody ,@header ,@body)))))
1423 (if (= nfooter 1)
1424 footer
1425 `((tr (td (table (tbody ,@footer))))))))
1426 (nconc `((tr (td (table (tbody ,@header)))))
1427 (if (= nbody nfooter)
1428 `((tr (td (table (tbody ,@body ,@footer)))))
1429 (nconc `((tr (td (table (tbody ,@body)))))
1430 (if (= nfooter 1)
1431 footer
1432 `((tr (td (table (tbody ,@footer))))))))))
1433 ;; header + body
3c066373 1434 (if (= nheader nbody)
2146e256
LMI
1435 `((tr (td (table (tbody ,@header ,@body)))))
1436 (if (= nheader 1)
1437 `(,@header (tr (td (table (tbody ,@body)))))
1438 `((tr (td (table (tbody ,@header))))
1439 (tr (td (table (tbody ,@body))))))))
1440 (if footer
1441 ;; body + footer
1442 (if (= nbody nfooter)
1443 `((tr (td (table (tbody ,@body ,@footer)))))
1444 (nconc `((tr (td (table (tbody ,@body)))))
1445 (if (= nfooter 1)
1446 footer
1447 `((tr (td (table (tbody ,@footer))))))))
1448 (if caption
1449 `((tr (td (table (tbody ,@body)))))
1450 body))))))
60568d74
LMI
1451 (when bgcolor
1452 (shr-colorize-region start (point) (cdr (assq 'color shr-stylesheet))
f462d10a
LMI
1453 bgcolor))
1454 ;; Finally, insert all the images after the table. The Emacs buffer
1455 ;; model isn't strong enough to allow us to put the images actually
1456 ;; into the tables.
1457 (when (zerop shr-table-depth)
1458 (dolist (elem (shr-find-elements cont 'img))
1459 (shr-tag-img (cdr elem))))))
6c769311 1460
130e977f
LMI
1461(defun shr-find-elements (cont type)
1462 (let (result)
1463 (dolist (elem cont)
1464 (cond ((eq (car elem) type)
1465 (push elem result))
1466 ((consp (cdr elem))
1467 (setq result (nconc (shr-find-elements (cdr elem) type) result)))))
1468 (nreverse result)))
71e691a5
G
1469
1470(defun shr-insert-table (table widths)
be2aa135
LMI
1471 (let* ((collapse (equal (cdr (assq 'border-collapse shr-stylesheet))
1472 "collapse"))
924d6997 1473 (shr-table-separator-length (if collapse 0 1))
be2aa135
LMI
1474 (shr-table-vertical-line (if collapse "" shr-table-vertical-line)))
1475 (unless collapse
1476 (shr-insert-table-ruler widths))
1477 (dolist (row table)
1478 (let ((start (point))
1479 (height (let ((max 0))
1480 (dolist (column row)
1481 (setq max (max max (cadr column))))
1482 max)))
1483 (dotimes (i height)
1484 (shr-indent)
1485 (insert shr-table-vertical-line "\n"))
1486 (dolist (column row)
1487 (goto-char start)
1488 (let ((lines (nth 2 column)))
1489 (dolist (line lines)
1490 (end-of-line)
1491 (insert line shr-table-vertical-line)
1492 (forward-line 1))
1493 ;; Add blank lines at padding at the bottom of the TD,
1494 ;; possibly.
1495 (dotimes (i (- height (length lines)))
1496 (end-of-line)
1497 (let ((start (point)))
1498 (insert (make-string (string-width (car lines)) ? )
1499 shr-table-vertical-line)
1500 (when (nth 4 column)
1501 (shr-add-font start (1- (point))
1502 (list :background (nth 4 column)))))
1503 (forward-line 1)))))
1504 (unless collapse
1505 (shr-insert-table-ruler widths)))))
71e691a5
G
1506
1507(defun shr-insert-table-ruler (widths)
924d6997
G
1508 (when shr-table-horizontal-line
1509 (when (and (bolp)
1510 (> shr-indentation 0))
1511 (shr-indent))
1512 (insert shr-table-corner)
1513 (dotimes (i (length widths))
1514 (insert (make-string (aref widths i) shr-table-horizontal-line)
1515 shr-table-corner))
1516 (insert "\n")))
71e691a5 1517
160ae063 1518(defun shr-table-widths (table natural-table suggested-widths)
a7dcc87b
G
1519 (let* ((length (length suggested-widths))
1520 (widths (make-vector length 0))
1521 (natural-widths (make-vector length 0)))
71e691a5
G
1522 (dolist (row table)
1523 (let ((i 0))
1524 (dolist (column row)
160ae063
LMI
1525 (aset widths i (max (aref widths i) column))
1526 (setq i (1+ i)))))
1527 (dolist (row natural-table)
1528 (let ((i 0))
1529 (dolist (column row)
1530 (aset natural-widths i (max (aref natural-widths i) column))
a7dcc87b 1531 (setq i (1+ i)))))
863b61d6
KY
1532 (let ((extra (- (apply '+ (append suggested-widths nil))
1533 (apply '+ (append widths nil))))
a7dcc87b 1534 (expanded-columns 0))
160ae063
LMI
1535 ;; We have extra, unused space, so divide this space amongst the
1536 ;; columns.
a7dcc87b 1537 (when (> extra 0)
160ae063
LMI
1538 ;; If the natural width is wider than the rendered width, we
1539 ;; want to allow the column to expand.
a7dcc87b 1540 (dotimes (i length)
a7dcc87b
G
1541 (when (> (aref natural-widths i) (aref widths i))
1542 (setq expanded-columns (1+ expanded-columns))))
1543 (dotimes (i length)
1544 (when (> (aref natural-widths i) (aref widths i))
1545 (aset widths i (min
160ae063 1546 (aref natural-widths i)
a7dcc87b
G
1547 (+ (/ extra expanded-columns)
1548 (aref widths i))))))))
71e691a5
G
1549 widths))
1550
1551(defun shr-make-table (cont widths &optional fill)
c74cb344
G
1552 (or (cadr (assoc (list cont widths fill) shr-content-cache))
1553 (let ((data (shr-make-table-1 cont widths fill)))
1554 (push (list (list cont widths fill) data)
1555 shr-content-cache)
1556 data)))
1557
1558(defun shr-make-table-1 (cont widths &optional fill)
be2aa135 1559 (let ((trs nil)
924d6997
G
1560 (shr-inhibit-decoration (not fill))
1561 (rowspans (make-vector (length widths) 0))
1562 width colspan)
71e691a5
G
1563 (dolist (row cont)
1564 (when (eq (car row) 'tr)
a0ec382a
LMI
1565 (let ((tds nil)
1566 (columns (cdr row))
1567 (i 0)
924d6997 1568 (width-column 0)
a0ec382a
LMI
1569 column)
1570 (while (< i (length widths))
924d6997
G
1571 ;; If we previously had a rowspan definition, then that
1572 ;; means that we now have a "missing" td/th element here.
1573 ;; So just insert a dummy, empty one to (sort of) emulate
1574 ;; rowspan.
1575 (setq column
1576 (if (zerop (aref rowspans i))
1577 (pop columns)
1578 (aset rowspans i (1- (aref rowspans i)))
1579 '(td)))
a0ec382a 1580 (when (or (memq (car column) '(td th))
924d6997
G
1581 (not column))
1582 (when (cdr (assq :rowspan (cdr column)))
1583 (aset rowspans i (+ (aref rowspans i)
1584 (1- (string-to-number
1585 (cdr (assq :rowspan (cdr column))))))))
970ad972
G
1586 ;; Sanity check for invalid column-spans.
1587 (when (>= width-column (length widths))
1588 (setq width-column 0))
924d6997
G
1589 (setq width
1590 (if column
1591 (aref widths width-column)
2fae38e5 1592 10))
924d6997
G
1593 (when (and fill
1594 (setq colspan (cdr (assq :colspan (cdr column)))))
2122cb6d
LMI
1595 (setq colspan (min (string-to-number colspan)
1596 ;; The colspan may be wrong, so
1597 ;; truncate it to the length of the
1598 ;; remaining columns.
1599 (- (length widths) i)))
924d6997
G
1600 (dotimes (j (1- colspan))
1601 (if (> (+ i 1 j) (1- (length widths)))
1602 (setq width (aref widths (1- (length widths))))
1603 (setq width (+ width
1604 shr-table-separator-length
1605 (aref widths (+ i 1 j))))))
1606 (setq width-column (+ width-column (1- colspan))))
1607 (when (or column
1608 (not fill))
1609 (push (shr-render-td (cdr column) width fill)
1610 tds))
1611 (setq i (1+ i)
1612 width-column (1+ width-column))))
71e691a5
G
1613 (push (nreverse tds) trs))))
1614 (nreverse trs)))
1615
1616(defun shr-render-td (cont width fill)
04db63bc 1617 (with-temp-buffer
60568d74
LMI
1618 (let ((bgcolor (cdr (assq :bgcolor cont)))
1619 (fgcolor (cdr (assq :fgcolor cont)))
1620 (style (cdr (assq :style cont)))
1621 (shr-stylesheet shr-stylesheet)
7304e4dd 1622 actual-colors)
60568d74
LMI
1623 (when style
1624 (setq style (and (string-match "color" style)
1625 (shr-parse-style style))))
1626 (when bgcolor
1627 (setq style (nconc (list (cons 'background-color bgcolor)) style)))
1628 (when fgcolor
1629 (setq style (nconc (list (cons 'color fgcolor)) style)))
1630 (when style
1631 (setq shr-stylesheet (append style shr-stylesheet)))
c74cb344
G
1632 (let ((shr-width width)
1633 (shr-indentation 0))
1634 (shr-descend (cons 'td cont)))
1635 ;; Delete padding at the bottom of the TDs.
1636 (delete-region
1637 (point)
1638 (progn
1639 (skip-chars-backward " \t\n")
1640 (end-of-line)
1641 (point)))
60568d74
LMI
1642 (goto-char (point-min))
1643 (let ((max 0))
1644 (while (not (eobp))
1645 (end-of-line)
1646 (setq max (max max (current-column)))
1647 (forward-line 1))
1648 (when fill
1649 (goto-char (point-min))
1650 ;; If the buffer is totally empty, then put a single blank
1651 ;; line here.
1652 (if (zerop (buffer-size))
1653 (insert (make-string width ? ))
1654 ;; Otherwise, fill the buffer.
544d4594
LMI
1655 (let ((align (cdr (assq :align cont)))
1656 length)
1657 (while (not (eobp))
1658 (end-of-line)
1659 (setq length (- width (current-column)))
1660 (when (> length 0)
1661 (cond
1662 ((equal align "right")
1663 (beginning-of-line)
1664 (insert (make-string length ? )))
1665 ((equal align "center")
1666 (insert (make-string (/ length 2) ? ))
1667 (beginning-of-line)
1668 (insert (make-string (- length (/ length 2)) ? )))
1669 (t
1670 (insert (make-string length ? )))))
1671 (forward-line 1))))
abb97fbb 1672 (when style
ec72bf63
G
1673 (setq actual-colors
1674 (shr-colorize-region
1675 (point-min) (point-max)
1676 (cdr (assq 'color shr-stylesheet))
1677 (cdr (assq 'background-color shr-stylesheet))))))
60568d74
LMI
1678 (if fill
1679 (list max
1680 (count-lines (point-min) (point-max))
1681 (split-string (buffer-string) "\n")
7304e4dd 1682 nil
ec72bf63 1683 (car actual-colors))
2122cb6d 1684 max)))))
a7dcc87b 1685
160ae063 1686(defun shr-buffer-width ()
a7dcc87b 1687 (goto-char (point-min))
160ae063 1688 (let ((max 0))
a7dcc87b
G
1689 (while (not (eobp))
1690 (end-of-line)
160ae063 1691 (setq max (max max (current-column)))
a7dcc87b
G
1692 (forward-line 1))
1693 max))
130e977f 1694
71e691a5
G
1695(defun shr-pro-rate-columns (columns)
1696 (let ((total-percentage 0)
1697 (widths (make-vector (length columns) 0)))
1698 (dotimes (i (length columns))
a7dcc87b 1699 (setq total-percentage (+ total-percentage (aref columns i))))
71e691a5
G
1700 (setq total-percentage (/ 1.0 total-percentage))
1701 (dotimes (i (length columns))
1702 (aset widths i (max (truncate (* (aref columns i)
1703 total-percentage
a7dcc87b 1704 (- shr-width (1+ (length columns)))))
71e691a5
G
1705 10)))
1706 widths))
1707
1708;; Return a summary of the number and shape of the TDs in the table.
1709(defun shr-column-specs (cont)
1710 (let ((columns (make-vector (shr-max-columns cont) 1)))
1711 (dolist (row cont)
1712 (when (eq (car row) 'tr)
1713 (let ((i 0))
1714 (dolist (column (cdr row))
1715 (when (memq (car column) '(td th))
1716 (let ((width (cdr (assq :width (cdr column)))))
1717 (when (and width
5d852256
LMI
1718 (string-match "\\([0-9]+\\)%" width)
1719 (not (zerop (setq width (string-to-number
1720 (match-string 1 width))))))
1721 (aset columns i (/ width 100.0))))
130e977f 1722 (setq i (1+ i)))))))
71e691a5
G
1723 columns))
1724
1725(defun shr-count (cont elem)
1726 (let ((i 0))
1727 (dolist (sub cont)
1728 (when (eq (car sub) elem)
1729 (setq i (1+ i))))
1730 i))
1731
1732(defun shr-max-columns (cont)
1733 (let ((max 0))
1734 (dolist (row cont)
1735 (when (eq (car row) 'tr)
130e977f
LMI
1736 (setq max (max max (+ (shr-count (cdr row) 'td)
1737 (shr-count (cdr row) 'th))))))
71e691a5
G
1738 max))
1739
f3fd95db 1740(provide 'shr)
367f7f81 1741
b4543a28 1742;; Local Variables:
c38e0c97 1743;; coding: utf-8
b4543a28
G
1744;; End:
1745
367f7f81 1746;;; shr.el ends here