reddit / emacs

Reading newer epub ebooks with nov.el / shr rendering removes whitespace

525G7bKV · 2022-09-03 12:44
Hello, There is some strange rendering behavior around shr. When I start reading one of the newer epub ebooks from oreilly using nov.el, in some sentences the whitespace between two words is missing. For instance: `#+begin_example` `Wecan’t stress enough the importance of experimentation as you learn new concepts here and throughout the book.` `#+end_example` Here, the whitespace is missing between \`We\` and \`can't\`. So I did checked the xhtml source of the epub: `#+begin_src html` `<p>We<a data-type="indexterm" data-primary="code examples" data-secondary="downloading" id="idm45654236145288"/><a data-type="indexterm" data-primary="online resources" data-secondary="code examples" id="idm45654236144280"/><a data-type="indexterm" data-primary="GitHub, code examples from" id="idm45654236143336"/> can’t stress enough the importance of experimentation as you learn new concepts here and throughout the book.` `#+end_src` As you can see, there is an anchor tag between the words, but with whitespace between the words. So I started \`debug-on-entry\` on the function `libxml-parse-html-region` to see what happened with the dom. This is my debug log: `#+begin_example` `Debugger entered--entering a function:` `* undo-auto--undoable-change()` `* shr-insert(" can’t stress enough the importance of experimenta...")` `* shrface-tag-p((p nil "We" (a ((data-type . "indexterm") (data-primary . "code examples") (data-secondary . "downloading") (id . "idm45654236145288"))) (a ((data-type . "indexterm") (data-primary . "online resources") (data-secondary . "code examples") (id . "idm45654236144280"))) (a ((data-type . "indexterm") (data-primary . "GitHub, code examples from") (id . "idm45654236143336"))) " can’t stress enough the importance of experimenta..."))` `* shr-descend((p nil "We" (a ((data-type . "indexterm") (data-primary . "code examples") (data-secondary . "downloading") (id . "idm45654236145288"))) (a ((data-type . "indexterm") (data-primary . "online resources") (data-secondary . "code examples") (id . "idm45654236144280"))) (a ((data-type . "indexterm") (data-primary . "GitHub, code examples from") (id . "idm45654236143336"))) " can’t stress enough the importance of experimenta..."))` `* shr-tag-body((body nil (p nil "We" (a ((data-type . "indexterm") (data-primary . "code examples") (data-secondary . "downloading") (id . "idm45654236145288"))) (a ((data-type . "indexterm") (data-primary . "online resources") (data-secondary . "code examples") (id . "idm45654236144280"))) (a ((data-type . "indexterm") (data-primary . "GitHub, code examples from") (id . "idm45654236143336"))) " can’t stress enough the importance of experimenta...")))` `* shr-descend((body nil (p nil "We" (a ((data-type . "indexterm") (data-primary . "code examples") (data-secondary . "downloading") (id . "idm45654236145288"))) (a ((data-type . "indexterm") (data-primary . "online resources") (data-secondary . "code examples") (id . "idm45654236144280"))) (a ((data-type . "indexterm") (data-primary . "GitHub, code examples from") (id . "idm45654236143336"))) " can’t stress enough the importance of experimenta...")))` `* shr-tag-html((html nil (body nil (p nil "We" (a ((data-type . "indexterm") (data-primary . "code examples") (data-secondary . "downloading") (id . "idm45654236145288"))) (a ((data-type . "indexterm") (data-primary . "online resources") (data-secondary . "code examples") (id . "idm45654236144280"))) (a ((data-type . "indexterm") (data-primary . "GitHub, code examples from") (id . "idm45654236143336"))) " can’t stress enough the importance of experimenta..."))))` `* shr-descend((html nil (body nil (p nil "We" (a ((data-type . "indexterm") (data-primary . "code examples") (data-secondary . "downloading") (id . "idm45654236145288"))) (a ((data-type . "indexterm") (data-primary . "online resources") (data-secondary . "code examples") (id . "idm45654236144280"))) (a ((data-type . "indexterm") (data-primary . "GitHub, code examples from") (id . "idm45654236143336"))) " can’t stress enough the importance of experimenta..."))))` `* #<subr shr-insert-document>((html nil (body nil (p nil "We" (a ((data-type . "indexterm") (data-primary . "code examples") (data-secondary . "downloading") (id . "idm45654236145288"))) (a ((data-type . "indexterm") (data-primary . "online resources") (data-secondary . "code examples") (id . "idm45654236144280"))) (a ((data-type . "indexterm") (data-primary . "GitHub, code examples from") (id . "idm45654236143336"))) " can’t stress enough the importance of experimenta..."))))` `* apply(#<subr shr-insert-document> (html nil (body nil (p nil "We" (a ((data-type . "indexterm") (data-primary . "code examples") (data-secondary . "downloading") (id . "idm45654236145288"))) (a ((data-type . "indexterm") (data-primary . "online resources") (data-secondary . "code examples") (id . "idm45654236144280"))) (a ((data-type . "indexterm") (data-primary . "GitHub, code examples from") (id . "idm45654236143336"))) " can’t stress enough the importance of experimenta..."))))` `* shr-insert-document((html nil (body nil (p nil "We" (a ((data-type . "indexterm") (data-primary . "code examples") (data-secondary . "downloading") (id . "idm45654236145288"))) (a ((data-type . "indexterm") (data-primary . "online resources") (data-secondary . "code examples") (id . "idm45654236144280"))) (a ((data-type . "indexterm") (data-primary . "GitHub, code examples from") (id . "idm45654236143336"))) " can’t stress enough the importance of experimenta..."))))`   `shr-render-buffer(#<buffer test.xhtml>)` `funcall-interactively(shr-render-buffer #<buffer test.xhtml>)` `call-interactively(shr-render-buffer record nil)` `command-execute(shr-render-buffer record)`   `execute-extended-command(nil "shr-render-buffer" "shr-re")` `funcall-interactively(execute-extended-command nil "shr-render-buffer" "shr-re")` `call-interactively(execute-extended-command nil nil)` `command-execute(execute-extended-command)` `#+end_example` The whitespace is inserted correctly. But after calling `undo-auto--undoable-change()` the whitespace was removed in the \*html\* buffer. What is happening here? Thank you!
525G7bKV · 2022-09-03 13:16
I also tested it with the following html code: `<p class="standard">Das Akronym CRISP-DM<a class="indexanchor" id="i03_02"/><a class="indexanchor" id="i03_03"/> steht für <span class="italic">` This html results in a `shr-insert(" steht für ")` as lisp code, but results as "Das Akronym CRISP-DMsteht für" It seems shr always removes the leading whitespace of a string after it "inserted" it into the buffer?!
flexibeast · 2022-09-03 13:27
i can reproduce this on my system, using [my epub-create script](https://github.com/flexibeast/epub-create) i released just the other day. :-) What i've found is that adding a space after "We" (and making sure the para is closed off with a `</p>`) results in the output having appropriate spacing, even if the space before "can't" is removed. So it's just the space before "can't" that's getting stripped; maybe `shr` removes all leading whitespace between the `>` of opening element and the element contents. Looking at the source of `shr-insert`, we have: (goto-char start) (when (looking-at "[ \t\n\r]+") (replace-match "" t t)) and indeed, changing that so that the second line is: (when (looking-at "[\t\n\r]+") solves the problem for me.
525G7bKV · 2022-09-03 13:33
Thank you so much! It really drives me crazy to read ebooks with this "error". I assumed the function shr-insert is removing leading whitespace, but i couldnt prove it.
flexibeast · 2022-09-03 13:33
:-) You're most welcome!
wasamasa · 2022-09-03 14:07
This smells like a shr.el bug that should be reported. Minimal broken example: (with-current-buffer "foo" (erase-buffer) (insert "<p>A<a id=\"x\"></a> B") (shr-render-region (point-min) (point-max))) It only happens when the `a` tag has an `id` attribute.