X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/821b6002127fba1e5b57d39e63eabd0ae189f6af..1dd4f26ab6c1f14628d9fcf03b0cca7e54d52302:/lisp/net/shr.el diff --git a/lisp/net/shr.el b/lisp/net/shr.el index 9d88d1ff44..d24f0d3753 100644 --- a/lisp/net/shr.el +++ b/lisp/net/shr.el @@ -1,6 +1,6 @@ ;;; shr.el --- Simple HTML Renderer -;; Copyright (C) 2010-2015 Free Software Foundation, Inc. +;; Copyright (C) 2010-2016 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: html @@ -57,12 +57,18 @@ fit these criteria." :group 'shr :type '(choice (const nil) regexp)) -(defcustom shr-use-fonts nil +(defcustom shr-use-fonts t "If non-nil, use proportional fonts for text." :version "25.1" :group 'shr :type 'boolean) +(defcustom shr-use-colors t + "If non-nil, respect color specifications in the HTML." + :version "25.2" + :group 'shr + :type 'boolean) + (defcustom shr-table-horizontal-line nil "Character used to draw horizontal table lines. If nil, don't draw horizontal table lines." @@ -135,6 +141,14 @@ cid: URL as the argument.") (defvar shr-inhibit-images nil "If non-nil, inhibit loading images.") +(defvar shr-external-rendering-functions nil + "Alist of tag/function pairs used to alter how shr renders certain tags. +For instance, eww uses this to alter rendering of title, forms +and other things: +((title . eww-tag-title) + (form . eww-tag-form) + ...)") + ;;; Internal variables. (defvar shr-folding-mode nil) @@ -150,9 +164,7 @@ cid: URL as the argument.") (defvar shr-depth 0) (defvar shr-warning nil) (defvar shr-ignore-cache nil) -(defvar shr-external-rendering-functions nil) (defvar shr-target-id nil) -(defvar shr-inhibit-decoration nil) (defvar shr-table-separator-length 1) (defvar shr-table-separator-pixel-width 0) (defvar shr-table-id nil) @@ -178,7 +190,7 @@ cid: URL as the argument.") ;; Public functions and commands. (declare-function libxml-parse-html-region "xml.c" - (start end &optional base-url)) + (start end &optional base-url discard-comments)) (defun shr-render-buffer (buffer) "Display the HTML rendering of the current buffer." @@ -204,6 +216,12 @@ cid: URL as the argument.") (goto-char begin) (shr-insert-document dom)))) +(defun shr--have-one-fringe-p () + "Return non-nil if we know at least one of the fringes has non-zero width." + (and (fboundp 'fringe-columns) + (or (not (zerop (fringe-columns 'right))) + (not (zerop (fringe-columns 'left)))))) + ;;;###autoload (defun shr-insert-document (dom) "Render the parsed document DOM into the current buffer. @@ -223,10 +241,29 @@ DOM should be a parse tree as generated by (if (not shr-use-fonts) shr-width (* shr-width (frame-char-width)))) + ;; We need to adjust the available + ;; width for when the user disables + ;; the fringes, which will cause the + ;; display engine usurp one column for + ;; the continuation glyph. (if (not shr-use-fonts) - (- (window-width) 2) - (- (window-pixel-width) - (* (frame-fringe-width) 2)))))) + (- (window-body-width) 1 + (if (and (null shr-width) + (not (shr--have-one-fringe-p))) + 0 + 1)) + (- (window-body-width nil t) + (* 2 (frame-char-width)) + (if (and (null shr-width) + (not (shr--have-one-fringe-p))) + (* (frame-char-width) 2) + 0))))) + bidi-display-reordering) + ;; If the window was hscrolled for some reason, shr-fill-lines + ;; below will misbehave, because it silently assumes that it + ;; starts with a non-hscrolled window (vertical-motion will move + ;; to a wrong place otherwise). + (set-window-hscroll nil 0) (shr-descend dom) (shr-fill-lines start (point)) (shr-remove-trailing-whitespace start (point)) @@ -285,13 +322,25 @@ redirects somewhere else." (defun shr-next-link () "Skip to the next link." (interactive) - (let ((skip (text-property-any (point) (point-max) 'help-echo nil))) - (if (or (eobp) - (not (setq skip (text-property-not-all skip (point-max) - 'help-echo nil)))) - (message "No next link") + (let ((current (get-text-property (point) 'shr-url)) + (start (point)) + skip) + (while (and (not (eobp)) + (equal (get-text-property (point) 'shr-url) current)) + (forward-char 1)) + (cond + ((and (not (eobp)) + (get-text-property (point) 'shr-url)) + ;; The next link is adjacent. + (message "%s" (get-text-property (point) 'help-echo))) + ((or (eobp) + (not (setq skip (text-property-not-all (point) (point-max) + 'shr-url nil)))) + (goto-char start) + (message "No next link")) + (t (goto-char skip) - (message "%s" (get-text-property (point) 'help-echo))))) + (message "%s" (get-text-property (point) 'help-echo)))))) (defun shr-previous-link () "Skip to the previous link." @@ -398,17 +447,16 @@ size, and full-buffer size." (defun shr-descend (dom) (let ((function - (or - ;; Allow other packages to override (or provide) rendering - ;; of elements. - (cdr (assq (dom-tag dom) shr-external-rendering-functions)) - (intern (concat "shr-tag-" (symbol-name (dom-tag dom))) obarray))) + (intern (concat "shr-tag-" (symbol-name (dom-tag dom))) obarray)) + ;; Allow other packages to override (or provide) rendering + ;; of elements. + (external (cdr (assq (dom-tag dom) shr-external-rendering-functions))) (style (dom-attr dom 'style)) (shr-stylesheet shr-stylesheet) (shr-depth (1+ shr-depth)) (start (point))) - ;; shr uses about 12 frames per nested node. - (if (> shr-depth (/ max-specpdl-size 12)) + ;; shr uses many frames per nested node. + (if (> shr-depth (/ max-specpdl-size 15)) (setq shr-warning "Too deeply nested to render properly; consider increasing `max-specpdl-size'") (when style (if (string-match "color\\|display\\|border-collapse" style) @@ -417,9 +465,12 @@ size, and full-buffer size." (setq style nil))) ;; If we have a display:none, then just ignore this part of the DOM. (unless (equal (cdr (assq 'display shr-stylesheet)) "none") - (if (fboundp function) - (funcall function dom) - (shr-generic dom)) + (cond (external + (funcall external dom)) + ((fboundp function) + (funcall function dom)) + (t + (shr-generic dom))) (when (and shr-target-id (equal (dom-attr dom 'id) shr-target-id)) ;; If the element was empty, we don't have anything to put the @@ -440,8 +491,17 @@ size, and full-buffer size." (with-temp-buffer (let ((shr-indentation 0) (shr-start nil) - (shr-internal-width (- (window-pixel-width) - (* (frame-fringe-width) 2)))) + (shr-internal-width (- (window-body-width nil t) + (* 2 (frame-char-width)) + ;; Adjust the window width for when + ;; the user disables the fringes, + ;; which causes the display engine + ;; to usurp one column for the + ;; continuation glyph. + (if (and (null shr-width) + (not (shr--have-one-fringe-p))) + (* (frame-char-width) 2) + 0)))) (shr-insert text) (buffer-string))))) @@ -621,7 +681,9 @@ size, and full-buffer size." ;; There's no breakable point, so we give it up. (let (found) (goto-char bp) - (unless shr-kinsoku-shorten + ;; Don't overflow the window edge, even if + ;; shr-kinsoku-shorten is nil. + (unless (or shr-kinsoku-shorten (null shr-width)) (while (setq found (re-search-forward "\\(\\c>\\)\\| \\|\\c<\\|\\c|" (line-end-position) 'move))) @@ -633,9 +695,12 @@ size, and full-buffer size." ;; Don't put kinsoku-bol characters at the beginning of a line, ;; or kinsoku-eol characters at the end of a line. (cond - (shr-kinsoku-shorten + ;; Don't overflow the window edge, even if shr-kinsoku-shorten + ;; is nil. + ((or shr-kinsoku-shorten (null shr-width)) (while (and (not (memq (preceding-char) (list ?\C-@ ?\n ? ))) - (shr-char-kinsoku-eol-p (preceding-char))) + (or (shr-char-kinsoku-eol-p (preceding-char)) + (shr-char-kinsoku-bol-p (following-char)))) (backward-char 1)) (when (setq failed (<= (point) start)) ;; There's no breakable point that doesn't violate kinsoku, @@ -709,6 +774,9 @@ size, and full-buffer size." shr-base)) (when (zerop (length url)) (setq url nil)) + ;; Strip leading whitespace + (and url (string-match "\\`\\s-+" url) + (setq url (substring url (match-end 0)))) (cond ((or (not url) (not base) (string-match "\\`[a-z]*:" url)) @@ -780,16 +848,15 @@ size, and full-buffer size." ;; blank text at the start of the line, and the newline at the end, to ;; avoid ugliness. (defun shr-add-font (start end type) - (unless shr-inhibit-decoration - (save-excursion - (goto-char start) - (while (< (point) end) - (when (bolp) - (skip-chars-forward " ")) - (add-face-text-property (point) (min (line-end-position) end) type t) - (if (< (line-end-position) end) - (forward-line 1) - (goto-char end)))))) + (save-excursion + (goto-char start) + (while (< (point) end) + (when (bolp) + (skip-chars-forward " ")) + (add-face-text-property (point) (min (line-end-position) end) type t) + (if (< (line-end-position) end) + (forward-line 1) + (goto-char end))))) (defun shr-mouse-browse-url (ev) "Browse the URL under the mouse cursor." @@ -948,6 +1015,9 @@ Return a string with image data." (search-forward "\r\n\r\n" nil t)) (shr-parse-image-data))))) +(declare-function libxml-parse-xml-region "xml.c" + (start end &optional base-url discard-comments)) + (defun shr-parse-image-data () (let ((data (buffer-substring (point) (point-max))) (content-type @@ -1038,8 +1108,9 @@ ones, in case fg and bg are nil." (shr-color-visible bg fg))))))) (defun shr-colorize-region (start end fg &optional bg) - (when (and (not shr-inhibit-decoration) - (or fg bg)) + (when (and shr-use-colors + (or fg bg) + (>= (display-color-cells) 88)) (let ((new-colors (shr-color-check fg bg))) (when new-colors (when fg @@ -1066,6 +1137,15 @@ ones, in case fg and bg are nil." ;;; Tag-specific rendering rules. +(defun shr-tag-html (dom) + (let ((dir (dom-attr dom 'dir))) + (cond + ((equal dir "ltr") + (setq bidi-paragraph-direction 'left-to-right)) + ((equal dir "rtl") + (setq bidi-paragraph-direction 'right-to-left)))) + (shr-generic dom)) + (defun shr-tag-body (dom) (let* ((start (point)) (fgcolor (or (dom-attr dom 'fgcolor) (dom-attr dom 'text))) @@ -1116,7 +1196,9 @@ ones, in case fg and bg are nil." (defun shr-tag-svg (dom) (when (and (image-type-available-p 'svg) - (not shr-inhibit-images)) + (not shr-inhibit-images) + (dom-attr dom 'width) + (dom-attr dom 'height)) (funcall shr-put-image-function (list (shr-dom-to-xml dom) 'image/svg+xml) "SVG Image"))) @@ -1209,8 +1291,7 @@ ones, in case fg and bg are nil." (shr-ensure-newline) (insert " ")) (put-text-property start (1+ start) 'shr-target-id shr-target-id)) - (when (and url - (not shr-inhibit-decoration)) + (when url (shr-urlify (or shr-start start) (shr-expand-url url) title)))) (defun shr-tag-object (dom) @@ -1535,19 +1616,30 @@ The preference is a float determined from `shr-prefer-media-type'." ;; Then render the table again with these new "hard" widths. (shr-insert-table (shr-make-table dom sketch-widths t) sketch-widths))) +(defun shr-table-body (dom) + (let ((tbodies (dom-by-tag dom 'tbody))) + (cond + ((null tbodies) + dom) + ((= (length tbodies) 1) + (car tbodies)) + (t + ;; Table with multiple tbodies. Convert into a single tbody. + `(tbody nil ,@(cl-reduce 'append + (mapcar 'dom-non-text-children tbodies))))))) + (defun shr-tag-table (dom) (shr-ensure-paragraph) (let* ((caption (dom-children (dom-child-by-tag dom 'caption))) (header (dom-non-text-children (dom-child-by-tag dom 'thead))) - (body (dom-non-text-children (or (dom-child-by-tag dom 'tbody) - dom))) + (body (dom-non-text-children (shr-table-body dom))) (footer (dom-non-text-children (dom-child-by-tag dom 'tfoot))) (bgcolor (dom-attr dom 'bgcolor)) (start (point)) (shr-stylesheet (nconc (list (cons 'background-color bgcolor)) shr-stylesheet)) (nheader (if header (shr-max-columns header))) - (nbody (if body (shr-max-columns body))) + (nbody (if body (shr-max-columns body) 0)) (nfooter (if footer (shr-max-columns footer)))) (if (and (not caption) (not header) @@ -1802,7 +1894,6 @@ The preference is a float determined from `shr-prefer-media-type'." (defun shr-make-table-1 (dom widths &optional fill) (let ((trs nil) - (shr-inhibit-decoration (not fill)) (rowspans (make-vector (length widths) 0)) (colspan-remaining 0) colspan-width colspan-count @@ -2049,8 +2140,4 @@ The preference is a float determined from `shr-prefer-media-type'." (provide 'shr) -;; Local Variables: -;; coding: utf-8 -;; End: - ;;; shr.el ends here