From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Yuan Fu Newsgroups: gmane.emacs.devel Subject: Re: HTML info Date: Sun, 26 Dec 2021 01:50:50 -0800 Message-ID: <4CE3CBAC-752F-4359-85CE-841E4654E3A5@gmail.com> References: <87zgosdbo1.fsf@gnus.org> <86mtkszhnq.fsf@mail.linkov.net> <87o858e5r2.fsf@gnu.org> <86fsqjcqd8.fsf@mail.linkov.net> <87zgor41oc.fsf@gnus.org> <86lf0b6uf0.fsf@mail.linkov.net> <87v8zf40qw.fsf@gnus.org> <8635mj5ept.fsf@mail.linkov.net> <83pmpnt7s5.fsf@gnu.org> <83ilvft6ru.fsf@gnu.org> <91625EFE-6771-4C17-B388-A0E3DBC3A9E6@gmail.com> <86mtkqjtq5.fsf@mail.linkov.net> <864k6wo97k.fsf@mail.linkov.net> Mime-Version: 1.0 (Mac OS X Mail 15.0 \(3693.40.0.1.81\)) Content-Type: multipart/mixed; boundary="Apple-Mail=_05731DD1-B033-4B3F-9C68-F31293421655" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="36627"; mail-complaints-to="usenet@ciao.gmane.io" Cc: Lars Ingebrigtsen , tsdh@gnu.org, Eli Zaretskii , Stefan Monnier , emacs-devel@gnu.org To: Juri Linkov Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Sun Dec 26 10:52:33 2021 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1n1QCf-0009L3-Fa for ged-emacs-devel@m.gmane-mx.org; Sun, 26 Dec 2021 10:52:33 +0100 Original-Received: from localhost ([::1]:51136 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1n1QCe-00016d-64 for ged-emacs-devel@m.gmane-mx.org; Sun, 26 Dec 2021 04:52:32 -0500 Original-Received: from eggs.gnu.org ([209.51.188.92]:36034) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1n1QB6-0000K7-51 for emacs-devel@gnu.org; Sun, 26 Dec 2021 04:50:56 -0500 Original-Received: from [2607:f8b0:4864:20::433] (port=45955 helo=mail-pf1-x433.google.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1n1QB4-000714-Ek; Sun, 26 Dec 2021 04:50:55 -0500 Original-Received: by mail-pf1-x433.google.com with SMTP id u20so11202191pfi.12; Sun, 26 Dec 2021 01:50:52 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:message-id:mime-version:subject:date:in-reply-to:cc:to :references; bh=i3G8dOik2IxYOi9qG/QAO5HEMVkPErI1dltcPZb6TaA=; b=pYS0JN8c4ZwDDOh0GhRN4HnDUacn8Ll+kg7bUUQdwAVSJUGmhNuKWnmVVZ97Z9x5OP g0pWrbSRSDuHqR3L3s3ftXyic3RVB47scEwI1cjr0OcbkE95i8Rfqu3e6/9lnuxck99F rwJi4LHWvRiitgJC6n3qHEMYlJruiwrJ2Fre0LafHtSM6eqZ9qoWeg3/RE7AOyvGj/hd +oDcCV05dzV2CkSB7YODglqAyMEGNL7UTTSEuz29r33CZQHHgPQMQI5L1qeLM/oXFHbq a+gxepTZZpZjx0tMmU359vumL795p691LXU5MoOna0x/yh9x58raWgEQFeJvAF7n92G4 UImA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:message-id:mime-version:subject:date :in-reply-to:cc:to:references; bh=i3G8dOik2IxYOi9qG/QAO5HEMVkPErI1dltcPZb6TaA=; b=YNqh60FpnyL+pRZYgl9ZywXXUtLZM7g8UtEsaQZvPXg3eGMEvHJ/1tWOwQ+iDySdXQ FJZ7XgPnHLvKwK/Ea0sR06o7gLWCs32Ft2q/Z668Ow2l6EFMfw+wpflwFXE84MbE4nhI 4a8CaGHyOmOajX8uS+n3LaTdq07I8dQQvjSPIMQMvTWF9o4b7n1wqjTQrDBBD5dCpkbW j64cit2p2ZgviLpjHLDRApZ7Anw11OAITbrjTnS8P0uC0rr+zdSs/PXF2sOb8ahs+WZR o6owSGc2p9zzXl1XMX1jwBUU5vbRObx4nvPW9J6aPhYvFeb2/nP018Es8IVb4kY6ii8F 0rmA== X-Gm-Message-State: AOAM533igQmJ5fzsrD2vMgOzO9AeFB9RzaX5JVuTez1j+i2mCzTjsb73 cha+E5dM0h9haU+v7QCG2k4HImKxBp8= X-Google-Smtp-Source: ABdhPJyWHaYo2HBVKpK2BXPS252Ws5wSje9/HMYOoQMN53DoPDvWxYtmFN4ol7am/ie5bqlTciV1tQ== X-Received: by 2002:a05:6a00:c95:b0:4ba:8ef8:dbc5 with SMTP id a21-20020a056a000c9500b004ba8ef8dbc5mr13296890pfv.62.1640512251881; Sun, 26 Dec 2021 01:50:51 -0800 (PST) Original-Received: from smtpclient.apple ([2600:1700:2ec7:8c90:904a:f14:4472:e2c5]) by smtp.gmail.com with ESMTPSA id f4sm9625474pfj.25.2021.12.26.01.50.50 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Sun, 26 Dec 2021 01:50:51 -0800 (PST) In-Reply-To: <864k6wo97k.fsf@mail.linkov.net> X-Mailer: Apple Mail (2.3693.40.0.1.81) X-Host-Lookup-Failed: Reverse DNS lookup failed for 2607:f8b0:4864:20::433 (failed) Received-SPF: pass client-ip=2607:f8b0:4864:20::433; envelope-from=casouri@gmail.com; helo=mail-pf1-x433.google.com X-Spam_score_int: -12 X-Spam_score: -1.3 X-Spam_bar: - X-Spam_report: (-1.3 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, RDNS_NONE=0.793, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.io gmane.emacs.devel:283270 Archived-At: --Apple-Mail=_05731DD1-B033-4B3F-9C68-F31293421655 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 > On Dec 25, 2021, at 10:01 AM, Juri Linkov wrote: >=20 > Eli Zaretskii writes: >=20 >> I think a new package would be cleaner. It can always borrow ideas >> and even code from info*.el, as appropriate. >=20 > Lars Ingebrigtsen writes: >=20 >>> 3. Completely duplicate all top Info commands in a new package >>> info-html.el. >>=20 >> I think that's best. >=20 > Fine. Anyone wants to write such a package? It would be nice to > include it in the core to be able to display Emacs manuals with > variable pitch fonts. I don=E2=80=99t claim to be seriously working on it, but here is a POC, = I didn=E2=80=99t try to plug it into (dir), the only entry point right = now is html-info-lispref which will open the lisp reference at = /doc/lispref/elisp.html. You can click around and go next/prev/up but = there is no i/[/]/r/l/etc. One thing I noticed is that the HTML Info files lack a nice top level = menu. The HTML Top only has a (very) long TOC and a short TOC. The TOC's = only include the node name and leave out the short description. I=E2=80=99m on a Mac which is case-insensitive, and my texinfo still = have the bug where it drops index.html when merging Index.html and = index.html. So I can=E2=80=99t test multi-file manual=E2=80=99s Top node = (it=E2=80=99s in index.html). Besides that, single-file and multi-file = seem to both work fine. Yuan --Apple-Mail=_05731DD1-B033-4B3F-9C68-F31293421655 Content-Disposition: attachment; filename=html-info.el Content-Type: application/octet-stream; x-unix-mode=0644; name="html-info.el" Content-Transfer-Encoding: quoted-printable ;;;=20html-info.el=20---=20HTML=20Info=20=20-*-=20lexical-binding:=20t;=20= -*-=0A=0A;;=20Author:=20Yuan=20Fu=20=0A=0A;;;=20This=20= file=20is=20NOT=20part=20of=20GNU=20Emacs=0A=0A;;;=20Commentary:=0A=0A= ;;;=20Code:=0A=0A(require=20'pcase)=0A(require=20'dom)=0A(require=20= 'shr)=0A=0A;;;=20Errors=0A=0A(define-error=20'html-info-error=20= "Html-info=20generic=20error"=20'error)=0A(define-error=20= 'html-info-libxml-missing=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= "Libxml=20library=20reqired=20but=20missing"=20'html-info-error)=0A= (define-error=20'html-info-file-not-found=0A=20=20=20=20=20=20=20=20=20=20= =20=20=20=20"File=20not=20found"=20'html-info-error)=0A(define-error=20= 'html-info-not-full-path=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= "File=20path=20not=20a=20full=20path"=20'html-info-error)=0A=0A= (defvar-local=20html-info--current-file=20nil=0A=20=20"The=20path=20of=20= the=20currently=20visiting=20Info=20file.")=0A=0A(defvar=20= html-info--dom-cache=20nil=0A=20=20"An=20alist=20of=20(PATH=20.=20DOM).=0A= PATH=20is=20the=20path=20to=20an=20Info=20file,=20DOM=20is=20that=20= file=E2=80=99s=20DOM.")=0A=0A(defvar=20html-info--shr-override-map=0A=20=20= (let=20((map=20(make-sparse-keymap=20)))=0A=20=20=20=20= (set-keymap-parent=20map=20shr-map)=0A=20=20=20=20(define-key=20map=20= [remap=20shr-browse-url]=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= #'html-info--follow-link-at-point)=0A=20=20=20=20map)=0A=20=20"The=20= keymap=20used=20to=20override=20the=20map=20on=20shr=20links.")=0A=0A= (defvar=20html-info--dom-classes-of-node=0A=20=20'("chapter"=20"section"=20= "subsection"=0A=20=20=20=20"subsubsection"=20"subsubsubsection")=0A=20=20= "All=20possible=20class=20attributes=20of=20a=20node.")=0A=0A(defvar=20= html-info--navigation=20nil=0A=20=20"A=20list=20of=20three=20anchor=20= nodes.=0AContains=20the=20next,=20previous=20and=20up=20node=20of=20the=20= current=20node,=20in=0Athat=20order.=20=20Each=20anchor=20node=20is=20= like=20(a=20((href=20...))=20...).")=0A=0A(defun=20= html-info--expand-node-name=20(node-name)=0A=20=20"Expand=20NODE-NAME=20= according=20to=20Texinfo=20algorithm."=0A=20=20;;=20See=20= https://www.gnu.org/software/texinfo/manual/texinfo/html_node/HTML-Xref-No= de-Name-Expansion.html=0A=20=20(cl-labels=20((need-encoding=20(ch)=0A=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20(not=20(string-match=20(rx=20= (or=20letter=20digit=20"=20"))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (char-to-string=20ch)))))=0A=20=20=20=20(setq=20node-name=0A=20=20=20=20=20= =20=20=20=20=20(replace-regexp-in-string=20(rx=20(+=20space))=20"=20"=20= node-name))=0A=20=20=20=20(setq=20node-name=20(string-trim=20node-name))=0A= =20=20=20=20(setq=20node-name=0A=20=20=20=20=20=20=20=20=20=20(mapconcat=20= (lambda=20(ch)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20(if=20(need-encoding=20ch)=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(format=20"_%04x"=20ch)=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (char-to-string=20ch)))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20node-name))=0A=20=20=20=20(setq=20node-name=20= (string-replace=20"=20"=20"-"=20node-name))))=0A=0A(defun=20= html-info--check-full-path=20(path)=0A=20=20(unless=20= (file-name-absolute-p=20path)=0A=20=20=20=20(signal=20= 'html-info-not-full-path=20path)))=0A=0A(defun=20= html-info--dom-remove-subsections=20(dom)=0A=20=20"Remove=20subsections=20= from=20DOM.=0A=0AA=20node=20in=20HTML=20is=20a=20div,=20this=20div=20= contains=20not=20only=20the=20content=0Aof=20that=20node,=20but=20also=20= its=20sub-nodes.=20=20We=20don=E2=80=99t=20want=20to=20display=0Athe=20= content=20of=20the=20sub-nodes,=20so=20we=20remove=20them=20with=20this=0A= function."=0A=20=20(append=20(list=20(dom-tag=20dom)=20(dom-attributes=20= dom))=0A=20=20=20=20=20=20=20=20=20=20(cl-remove-if=20(lambda=20(node)=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20(and=20(consp=20node)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(eq=20(dom-tag=20node)=20= 'div)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20(member=20(dom-attr=20node=20'class)=0A=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20html-info--dom-classes-of-node)))=0A=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (dom-children=20dom))))=0A=0A(defun=20html-info--dom-remove-header=20= (dom)=0A=20=20"Remove=20the=20header=20of=20DOM=20and=20return=20(DOM=20= .=20(NEXT=20PREV=20UP)).=0A=0ANEXT,=20PREV,=20UP=20are=20the=20anchor=20= node=20()=20of=20the=0Aprevious,=20next=20and=20up=20= node,=20respectively."=0A=20=20;;=20We=20must=20not=20remove=20header=20= destructively=20because=20we=20cache=20the=20dom=0A=20=20;;=20object.=0A=20= =20(let*=20((dom=20(copy-tree=20dom))=0A=20=20=20=20=20=20=20=20=20= (header=20(car=20(dom-by-class=20dom=20"header")))=0A=20=20=20=20=20=20=20= =20=20(anchors=20(dom-by-tag=20header=20'a))=0A=20=20=20=20=20=20=20=20=20= (navi=0A=20=20=20=20=20=20=20=20=20=20;;=20ANCHORS=20is=20either=20(NEXT=20= PREV=20UP=20CONTENT=20INDEX)=0A=20=20=20=20=20=20=20=20=20=20;;=20or=20= (NEXT=20UP=20CONTENT=20INDEX)=0A=20=20=20=20=20=20=20=20=20=20;;=20or=20= (PREV=20UP=20CONTENT=20INDEX).=0A=20=20=20=20=20=20=20=20=20=20(cond=20= ((eq=20(length=20anchors)=205)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20(cl-subseq=20anchors=200=203))=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20;;=20Prev=20is=20missing.=0A=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20((string-match=20"Next"=20(dom-texts=20header))=0A=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(list=20(car=20anchors)=20= nil=20(nth=202=20anchors)))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20;;=20Next=20is=20missing.=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20((string-match=20"Prev"=20(dom-texts=20header))=0A=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20(cons=20nil=20(cl-subseq=20anchors=200=20= 2))))))=0A=20=20=20=20(dom-remove-node=20dom=20header)=0A=20=20=20=20= (cons=20dom=20navi)))=0A=0A(defun=20html-info--get-dom=20(file)=0A=20=20= "Return=20the=20DOM=20of=20Info=20file=20FILE."=0A=20=20(when=20(not=20= (file-exists-p=20file))=0A=20=20=20=20(signal=20= 'html-info-file-not-found=20file))=0A=20=20(if=20(fboundp=20= 'libxml-parse-html-region)=0A=20=20=20=20=20=20(or=20(alist-get=20file=20= html-info--dom-cache=20nil=20nil=20#'equal)=0A=20=20=20=20=20=20=20=20=20= =20(setf=20(alist-get=20file=20html-info--dom-cache=20nil=20nil=20= #'equal)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (with-temp-buffer=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (insert-file-contents=20file)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20(libxml-parse-html-region=20(point-min)=20(point-max)))))=0A=20= =20=20=20(signal=20'html-info-libxml-missing=20nil)))=0A=0A(defun=20= html-info--dom-bfs=20(dom=20pred)=0A=20=20"Search=20DOM=20breadth-first=20= with=20PRED.=20Return=20the=20first=20match.=0APRED=20takes=20a=20single=20= argument,=20the=20node."=0A=20=20(declare=20(indent=201))=0A=20=20(catch=20= 'found=0A=20=20=20=20(let=20((queue=20(dom-children=20dom)))=0A=20=20=20=20= =20=20(while=20queue=0A=20=20=20=20=20=20=20=20(let=20((node=20(pop=20= queue)))=0A=20=20=20=20=20=20=20=20=20=20(when=20(consp=20node)=0A=20=20=20= =20=20=20=20=20=20=20=20=20(if=20(funcall=20pred=20node)=0A=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20(throw=20'found=20node)=0A=20=20=20=20=20= =20=20=20=20=20=20=20=20=20(setq=20queue=20(append=20queue=20= (dom-children=20node))))))))))=0A=0A(defun=20html-info--find-node-1=20= (file=20id=20strict-case)=0A=20=20"Return=20the=20DOM=20of=20the=20node=20= with=20ID=20in=20FILE.=0AFirst=20search=20for=20ID=20case-sensitively,=20= then=20case-insensitively=0A=20(unless=20STRICT-CASE=20is=20t).=20=20If=20= ID=20is=20nil,=20we=20find=20the=20first=20node=0A=20which=20its=20class=20= is=20one=20of=20chapter,=20section,=20subsection,=20subsubsection."=0A=20= =20;;=20We=20must=20find=20a=20chapter,=20section,=20subsection=20or=20= subsubsection=20to=0A=20=20;;=20be=20NODE,=20and=20must=20not=20find=20= an=20arbitrary=20node=20that=20contains=20the=0A=20=20;;=20chapter,=20= section,=20etc=20that=20we=20actually=20want.=20=20That=20is=20because=20= we=0A=20=20;;=20use=20=E2=80=98html-info--dom-remove-subsections=E2=80=99=20= to=20remove=20any=20chapter=20etc=0A=20=20;;=20that=E2=80=99s=20_under_=20= NODE.=20=20So=20if=20the=20target=20chapter=20etc=20is=20contained=0A=20=20= ;;=20in=20NODE=20rather=20than=20being=20NODE,=20it=20will=20be=20= removed.=20=20And=20that=E2=80=99s=0A=20=20;;=20bad.=20=20If=20ID=20is=20= non-nil,=20NODE=20must=20be=20a=20chapter=20etc.=20=20If=20ID=20is=0A=20=20= ;;=20nil,=20we=20find=20the=20first=20chapter=20etc=20and=20use=20that=20= (instead=20of=20the=0A=20=20;;=20top-level=20DOM).=0A=20=20(let=20((dom=20= (html-info--get-dom=20file)))=0A=20=20=20=20;;=20If=20we=20are=20= visiting=20the=20Top=20node,=20directly=20find=20shortcontents,=0A=20=20=20= =20;;=20skip=20=E2=80=98dom-by-id=E2=80=99,=20because=20it=20traverses=20= the=20whole=20DOM,=20and=20Top=0A=20=20=20=20;;=20node=20could=20be=20= very=20large=20(contains=20the=20entire=20manual=20when=20the=0A=20=20=20= =20;;=20manual=20is=20single-file).=0A=20=20=20=20(if=20(or=20(equal=20= (file-name-base=20file)=20"index")=0A=20=20=20=20=20=20=20=20=20=20=20=20= (equal=20id=20"Top"))=0A=20=20=20=20=20=20=20=20(let*=20((short-toc=0A=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(html-info--dom-bfs=20dom=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(lambda=20(node)=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(equal=20= (dom-attr=20node=20'class)=20"shortcontents"))))=0A=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20(long-toc=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20(html-info--dom-bfs=20dom=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20(lambda=20(node)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20(equal=20(dom-attr=20node=20'class)=20= "contents"))))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(links=20= (dom-by-tag=20short-toc=20'a))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20(real-links=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(mapcar=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(lambda=20(anchor)=0A=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(let=20((id=20= (substring=20(dom-attr=20anchor=20'href)=201)))=0A=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20`(li=20nil=20,@(dom-by-id=20= long-toc=20id))))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= links)))=0A=20=20=20=20=20=20=20=20=20=20`(ul=20nil=20,@real-links))=0A=20= =20=20=20=20=20(if=20id=0A=20=20=20=20=20=20=20=20=20=20(if-let=20((node=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(or=20(let=20= ((case-fold-search=20t))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20(car=20(dom-by-id=20dom=20(format=20"^%s$"=20= id))))=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20(if=20strict-case=20nil=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20(let=20((case-fold-search=20nil))=0A=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20(car=20(dom-by-id=20dom=20(format=20"^%s$"=20id))))))))=0A=20=20=20= =20=20=20=20=20=20=20=20=20=20=20(html-info--dom-remove-subsections=20= node)=0A=20=20=20=20=20=20=20=20=20=20=20=20nil)=0A=20=20=20=20=20=20=20=20= ;;=20ID=20is=20nil,=20find=20the=20first=20chapter,=20section,=20etc.=20=20= Search=0A=20=20=20=20=20=20=20=20;;=20breadth-first.=0A=20=20=20=20=20=20= =20=20(html-info--dom-remove-subsections=0A=20=20=20=20=20=20=20=20=20= (html-info--dom-bfs=20dom=0A=20=20=20=20=20=20=20=20=20=20=20(lambda=20= (node)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20(member=20(dom-attr=20= node=20'class)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20html-info--dom-classes-of-node))))))))=0A=0A(defun=20= html-info--find-node=20(current-file=20node=20&optional=20strict-case)=0A= =20=20"Find=20the=20NODE,=20return=20its=20DOM=20and=20file=20path.=0A=0A= Specifically,=20return=20(DOM=20.=20PATH).=0A=0ANODE=20can=20be=20either=20= a=20string=20(node=20name)=20or=20(FILE=20.=20ID),=20where=0AFILE=20is=20= the=20full=20path=20to=20the=20HTML=20Info=20file=20and=20ID=20is=20the=20= node=E2=80=99s=0AID.=20=20CURRENT-FILE=20is=20the=20full=20path=20to=20= the=20currently=20displayed=20HTML=0AInfo=20file.=0A=0AFirst=20looks=20= for=20a=20case-sensitive=20match=20for=20the=20node=20part=20of=0A= NODE-NAME;=20if=20none=20is=20found=20it=20then=20tries=20a=20= case-insensitive=0Amatch=20\(unless=20STRICT-CASE=20is=20non-nil).=0A=0A= Return=20nil=20if=20no=20node=20is=20found."=0A=20=20(if-let=20= ((node-dom=0A=20=20=20=20=20=20=20=20=20=20=20=20(if=20(stringp=20node)=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(html-info--find-node-1=0A= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20current-file=20= (html-info--expand-node-name=20node)=0A=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20strict-case)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= nil)))=0A=20=20=20=20=20=20(cons=20node-dom=20current-file)=0A=20=20=20=20= (pcase=20node=0A=20=20=20=20=20=20((pred=20stringp)=0A=20=20=20=20=20=20=20= (if-let=20((path=20(expand-file-name=0A=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20(concat=20(html-info--expand-node-name=20= node)=20".html")=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20(file-name-directory=20current-file)))=0A=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20(node-dom=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20(html-info--find-node-1=20path=20nil=20strict-case)))=0A= =20=20=20=20=20=20=20=20=20=20=20(cons=20node-dom=20path)=0A=20=20=20=20=20= =20=20=20=20nil))=0A=20=20=20=20=20=20(`(,file=20.=20,id)=0A=20=20=20=20=20= =20=20(html-info--check-full-path=20file)=0A=20=20=20=20=20=20=20(if-let=20= ((node-dom=20(html-info--find-node-1=20file=20id=20strict-case)))=0A=20=20= =20=20=20=20=20=20=20=20=20(cons=20node-dom=20file)=0A=20=20=20=20=20=20=20= =20=20nil)))))=0A=0A(defun=20html-info-goto-node=20(node=20&optional=20= fork=20strict-case)=0A=20=20"Go=20to=20Info=20node=20named=20NODE.=0A=0A= TODO:=20(FILENAME)NODE-NAME.=0ATODO:=20Completion.=0ATODO:=20Empty=20= NODE-NAME=20->=20top=20node.=0ATODO:=20FORK=20as=20a=20string.=0A=0ANODE=20= can=20be=20either=20a=20string=20(node=20name)=20or=20(FILE=20.=20ID),=20= where=0AFILE=20is=20the=20full=20path=20to=20the=20HTML=20Info=20file=20= and=20ID=20is=20the=20node=E2=80=99s=0AHTML=20id.=0A=0AFILE=20is=20the=20= full=20path=20to=20the=20file=20in=20where=20the=20node=20resides.=20=20= If=0Aomitted,=20FILE=20defaults=20to=20the=20currently=20visited=20Info=20= file.=0A=0AThis=20function=20first=20looks=20for=20a=20case-sensitive=20= match=20for=20the=20node=20part=0Aof=20NODE-NAME;=20if=20none=20is=20= found=20it=20then=20tries=20a=20case-insensitive=20match=0A\(unless=20= STRICT-CASE=20is=20non-nil)."=0A=20=20(pcase-let=20((`(,node-dom=20.=20= ,node-file)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (html-info--find-node=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= html-info--current-file=20node=20strict-case))=0A=20=20=20=20=20=20=20=20= =20=20=20=20=20=20(info-buffer=20(get-buffer-create=0A=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(if=20= fork=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20(format=20"*html=20info-%s*"=0A=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20(if=20(stringp=20node)=0A=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20node=20(cdr=20node)))=0A=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20"*html=20info*"))))=0A=20=20=20=20(when=20(null=20node-dom)=0A=20=20= =20=20=20=20(user-error=20"No=20such=20node=20or=20anchor:=20%s"=20= node))=0A=20=20=20=20(switch-to-buffer=20info-buffer)=0A=20=20=20=20= (pcase-let*=0A=20=20=20=20=20=20=20=20((`(,final-dom=20.=20,navi)=0A=20=20= =20=20=20=20=20=20=20=20(html-info--dom-remove-header=20node-dom))=0A=20=20= =20=20=20=20=20=20=20(inhibit-read-only=20t)=0A=20=20=20=20=20=20=20=20=20= (shr-map=20html-info--shr-override-map)=0A=20=20=20=20=20=20=20=20=20= (breadcrumb=20(if=20navi=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20(apply=20#'html-info--breadcrumbs=20navi)=0A=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20"TODO=20= breadcrumb=20for=20Top")))=0A=20=20=20=20=20=20(erase-buffer)=0A=20=20=20= =20=20=20(if=20Info-use-header-line=0A=20=20=20=20=20=20=20=20=20=20= (setq=20header-line-format=20breadcrumb)=0A=20=20=20=20=20=20=20=20= (insert=20breadcrumb))=0A=20=20=20=20=20=20(shr-insert-document=20= final-dom)=0A=20=20=20=20=20=20(unless=20(derived-mode-p=20= 'html-info-mode)=0A=20=20=20=20=20=20=20=20(html-info-mode))=0A=20=20=20=20= =20=20(setq=20html-info--current-file=20node-file)=0A=20=20=20=20=20=20= (setq=20html-info--navigation=20navi)=0A=20=20=20=20=20=20(goto-char=20= (point-min)))))=0A=0A(define-button-type=20'html-info-navigation-button=0A= =20=20'action=20#'html-info--button-follow-link=0A=20=20'mouse-action=20= #'html-info--button-follow-link=0A=20=20'help-echo=20"Go=20to=20node"=0A=20= =20'follow-link=20t)=0A=0A(defun=20html-info--breadcrumbs=20(next=20prev=20= up)=0A=20=20"Generate=20the=20breadcrumbs.=0ANEXT,=20PREV=20and=20UP=20= are=20anchor=20nodes=20of=20the=20previous,=20next=20and=20up=0Anode.=20=20= See=20=E2=80=98html-info--navigation=E2=80=99."=0A=20=20(cl-labels=20= ((node-name=20(dom)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (if=20(equal=20(dom-attr=20dom=20'href)=20"index.html")=0A=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20"Top"=20(dom-text=20dom))))=0A= =20=20=20=20(with-temp-buffer=0A=20=20=20=20=20=20(when=20next=0A=20=20=20= =20=20=20=20=20(insert=20"Next:=20")=0A=20=20=20=20=20=20=20=20= (insert-text-button=20(node-name=20next)=0A=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20'type=20= 'html-info-navigation-button=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20'target=20(dom-attr=20next=20= 'href)))=0A=20=20=20=20=20=20(when=20(and=20next=20prev)=0A=20=20=20=20=20= =20=20=20(insert=20",=20"))=0A=20=20=20=20=20=20(when=20prev=0A=20=20=20=20= =20=20=20=20(insert=20"Prev:=20")=0A=20=20=20=20=20=20=20=20= (insert-text-button=20(node-name=20prev)=0A=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20'type=20= 'html-info-navigation-button=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20'target=20(dom-attr=20prev=20= 'href)))=0A=20=20=20=20=20=20(insert=20",=20Up:=20")=0A=20=20=20=20=20=20= (insert-text-button=20(node-name=20up)=0A=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20'type=20= 'html-info-navigation-button=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20'target=20(dom-attr=20up=20'href))=0A= =20=20=20=20=20=20(buffer-string))))=0A=0A(defun=20= html-info--follow-link=20(url)=0A=20=20"Go=20to=20the=20node=20= represented=20by=20URL.=0AURL=20could=20be=20=E2=80=9Cfile=E2=80=9D,=20= or=20=E2=80=9Cfile#anchor=E2=80=9D=20or=20=E2=80=9C#anchor.=E2=80=9D"=0A=20= =20;;=20The=20"file#anchor"=20case.=0A=20=20(cl-labels=20((expand=20= (file)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= (expand-file-name=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= file=20(file-name-directory=20html-info--current-file))))=0A=20=20=20=20= (cond=20((string-match=20(rx=20(seq=20bol=20(group=20(+=20any))=20"#"=0A=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20(group=20(+=20any))=20eol))=0A=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20url)=0A=20=20=20=20= =20=20=20=20=20=20=20(html-info-goto-node=0A=20=20=20=20=20=20=20=20=20=20= =20=20(cons=20(expand=20(match-string=201=20url))=0A=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20(match-string=202=20url))))=0A=20=20=20=20= =20=20=20=20=20=20;;=20The=20"#anchor"=20case.=0A=20=20=20=20=20=20=20=20= =20=20((string-match=20(rx=20(seq=20bol=20"#"=20(group=20(+=20any))=20= eol))=20url)=0A=20=20=20=20=20=20=20=20=20=20=20(html-info-goto-node=0A=20= =20=20=20=20=20=20=20=20=20=20=20(cons=20html-info--current-file=20= (match-string=201=20url))))=0A=20=20=20=20=20=20=20=20=20=20;;=20The=20= "file"=20case.=0A=20=20=20=20=20=20=20=20=20=20(t=20(html-info-goto-node=20= (cons=20(expand=20url)=20nil))))))=0A=0A(defun=20= html-info--button-follow-link=20(button)=0A=20=20"Follow=20the=20link=20= in=20BUTTON."=0A=20=20(interactive)=0A=20=20(html-info--follow-link=20= (button-get=20button=20'target)))=0A=0A(defun=20= html-info--follow-link-at-point=20()=0A=20=20"Follow=20the=20link=20at=20= point."=0A=20=20(interactive)=0A=20=20(let=20((url=20(get-text-property=20= (point)=20'shr-url)))=0A=20=20=20=20(if=20(not=20url)=0A=20=20=20=20=20=20= =20=20(user-error=20"No=20link=20at=20point")=0A=20=20=20=20=20=20= (html-info--follow-link=20url))))=0A=0A(defun=20html-info-next=20()=0A=20= =20"Go=20to=20the=20=E2=80=9Cnext=E2=80=9D=20node,=20staying=20on=20the=20= same=20hierarchical=20level.=0AThis=20command=20doesn=E2=80=99t=20= descend=20into=20sub-nodes,=20like=0A= \\\\[html-info-forward-node]=20does."=0A=20=20= (interactive)=0A=20=20(if-let=20((next=20(nth=200=20= html-info--navigation)))=0A=20=20=20=20=20=20(html-info--follow-link=20= (dom-attr=20next=20'href))=0A=20=20=20=20(user-error=20"Node=20has=20no=20= next")))=0A=0A(defun=20html-info-prev=20()=0A=20=20"Go=20to=20the=20= =E2=80=9Cprevious=E2=80=9D=20node,=20staying=20on=20the=20same=20= hierarchical=20level.=0AThis=20command=20doesn't=20go=20up=20to=20the=20= parent=20node,=20like=0A= \\\\[html-info-backward-node]=20does."=0A=20=20= (interactive)=0A=20=20(if-let=20((prev=20(nth=201=20= html-info--navigation)))=0A=20=20=20=20=20=20(html-info--follow-link=20= (dom-attr=20prev=20'href))=0A=20=20=20=20(user-error=20"Node=20has=20no=20= previous")))=0A=0A(defun=20html-info-up=20()=0A=20=20"Go=20to=20the=20= superior=20node,=20staying=20on=20the=20same=20hierarchical=20level.=0A= This=20command=20doesn=E2=80=99t=20descend=20into=20sub-nodes,=20like=0A= \\\\[html-info-forward-node]=20does."=0A=20=20= (interactive)=0A=20=20(html-info--follow-link=0A=20=20=20(dom-attr=20= (nth=202=20html-info--navigation)=20'href)))=0A=0A(defvar=20= html-info-mode-map=0A=20=20(let=20((map=20(make-sparse-keymap)))=0A=20=20= =20=20(define-key=20map=20"n"=20#'html-info-next)=0A=20=20=20=20= (define-key=20map=20"p"=20#'html-info-prev)=0A=20=20=20=20(define-key=20= map=20"u"=20#'html-info-up)=0A=20=20=20=20map)=0A=20=20"Keymap=20for=20= =E2=80=98html-info-mode=E2=80=99.")=0A=0A(define-derived-mode=20= html-info-mode=20special-mode=0A=20=20"HTML=20Info"=0A=20=20"HTML=20Info=20= mode=20lets=20you=20browse=20HTML=20Info=20files.=0ADocumentation=20in=20= Info=20is=20divided=20into=20\"nodes\",=20each=20of=20which=0Adiscusses=20= one=20topic=20and=20contains=20references=20to=20other=20nodes=20which=0A= discuss=20related=20topics.=20=20Info=20has=20commands=20to=20follow=20= the=0Areferences=20and=20show=20you=20other=20nodes."=0A=20=20(setq=20= case-fold-search=20t)=0A=20=20(setq=20buffer-read-only=20t)=0A=20=20;;=20= TODO:=20tool-bar=0A=20=20;;=20TODO:=20desktop-save-buffer=0A=20=20;;=20= TODO:=20revert=20buffer=0A=20=20;;=20TODO:=20= bookmark-make-record-function=0A=20=20;;=20TODO:=20mode-line=0A=20=20= (setq-local=20widen-automatically=20nil)=0A=20=20(when=20Info-standalone=0A= =20=20=20=20(add-hook=20'quit-window-hook=20'save-buffers-kill-emacs=20= nil=20t)))=0A=0A(defun=20html-info-lispref=20()=0A=20=20"Open=20Emacs=20= Lisp=20Reference=20Info."=0A=20=20(interactive)=0A=20=20(let=20((lispref=20= (expand-file-name=20"doc/lispref/elisp.html"=0A=20=20=20=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20source-directory)))=0A=20=20=20=20(if=20(file-exists-p=20lispref)=0A=20= =20=20=20=20=20=20=20(let=20((html-info--current-file=0A=20=20=20=20=20=20= =20=20=20=20=20=20=20=20=20(if=20(file-directory-p=20lispref)=0A=20=20=20= =20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20(expand-file-name=20= "index.html"=20lispref)=0A=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20=20= =20lispref)))=0A=20=20=20=20=20=20=20=20=20=20(html-info-goto-node=20= "Top"))=0A=20=20=20=20=20=20(error=20"Cannot=20find=20lispref=20at=20%s"=20= lispref))))=0A=0A(provide=20'html-info)=0A=0A;;;=20html-info.el=20ends=20= here=0A=0A;;=20Local=20Variables:=0A;;=20sentence-end-double-space:=20t=0A= ;;=20End:=0A= --Apple-Mail=_05731DD1-B033-4B3F-9C68-F31293421655--