The Code Diarist

A diary about code

Automate html Links with Emacs Lisp

October 2, 2025

A LISP function is described for automatically producing and inserting navigational links from header tags, such as <h2>, in an html file for a web page.

The following list of links to headers on this page was created using the Emacs Lisp function presented here.

This post follows and addresses concerns voiced in the previous post, Use The Right Tool. In that one, I complained that Emacs Lisp fell short of a Linux utility, sed, when automating a certain editing function for html files.

After posting the article, I arrived at a solution which I present here. As this is a diary, I shall leave the previous article online. In fairness and gratitude to the people who did the work to give us Emacs and its Lisp language for free, I edited that article to disclose and point readers to this correction of some statements I made there.

By the way, what I have written here presumes a modest familiarity with using Emacs. This article must use, but could not begin to explain the meaning of, terms such as buffer or M-x h2a <RET>.

Consider the following segment of code from a hypothetical web page.

  <header>
    <h1">Searching Tag Contents</h1>
    <p>This is about searching for id-text and display-text in heading tags, e.g., &lt;h2&gt;.</p>;
  </header>
  <nav>
    <ul>
    </ul>
  </nav>
	
  <main>
    <h2 id="sub-heading-one">This Is Heading One</h2>
    <p>Some words relevant to topic number one</p>
    <h2 id="sub-heading-two">Topic Two Discussed Here</h2>
    <p>Discuss second topic at some length.</p>
    <h2 id="summary-conclusion">Summary and Conclusion</h2>
    <p>Wrap up a totally convincing fashion. Expect rewards and praise.</p>
    <p>(Of course, no one ever gets that.)</p>
  </main>
      

Look closely at the <nav> and <ul> tags. They mark the place where the navigational links will go when you run the function. If you plan to use the function with your own code, consider including at least the <ul> </ul> pair of tags.

The <h2> tags in the above listing have id attributes which allow the tags to become navigational landmarks. For example, the table of contents at the top of this page lists links to the <h2> tags within the document.

It is not difficult to write the navigational list. However, it can become tedious, and typographical errors can defeat the links. An automated editing procedure makes sense for this purpose.

Return to top of page

Defining the Lisp Function

The Lisp function listed below has worked well for me.

If you choose to try this script, copy the listing and paste it into an ordinary text file. Save the file with a name you can remember, and give the file name a suffix of ".el". For example, I saved my copy as h2a.el, because the name reminds me that it transforms <h2> tags into <a> tags, which establish links you can click on.

(defun h2a ()
  "Prepare and insert nav tags from header tags in html"
  (interactive)
  (let ()
    (goto-char (point-min))
    (setq insert-point (search-forward "<ul>"))
    (goto-char (point-min))
    (setq tag-number 1)
    (setq pos (re-search-forward
	"^.*<h2.*id=\"\\(.*\\)\".*>\\(.*\\)</h2>.*$"
        nil t tag-number))
    (while pos
      (setq id-text (match-string-no-properties 1))
      (setq tag-text (match-string-no-properties 2))
      (setq anchor
      (format "\n<li><a href=\"#%s\">%s</a></li>"
      id-text tag-text))
      (goto-char insert-point)
      (insert anchor)
      (setq insert-point (point))
      (setq tag-number (+ 1 tag-number))
      (goto-char (point-min))
      (setq pos (re-search-forward
	"^.*<h2.*id=\"\\(.*\\)\".*>\\(.*\\)</h2>.*$"
	nil t tag-number))
      )
    )
  )
      

The file that contains the above listing does not need to be open in Emacs. Instead, you will “load” it when you want to use it.

Return to top of page

Using the Lisp Function to Insert the Links

After saving the function in a file named, for example, h2a.el, it can be used with any html file you have open for editing in an Emacs buffer. The procedure has two steps.

Step 1 is to load the file. Loading does not bring the file into Emacs for editing. Rather, it tells Emacs to read the file and to evaluate (that is, get itself ready to use) the function it finds in there. For a file named h2a.el located in the current file directory, the instruction would be:

M-x load-file  h2a.el 
      

Step two is to run the function. In the listing above, the function is defined to be accessible by the name, “h2a”. The instruction is:

M-x h2a 
      

If all goes well, the links appear in the blink of an eye. They may be aligned to the left edge of the buffer initially. You can indent them to look nicer, if you wish.

  <nav>
    <ul>
<li><a href="#sub-heading-one">This Is Heading One</a></li>
<li><a href="#sub-heading-two">Topic Two Discussed Here</a></li>
<li><a href="#summary-conclusion">Summary and Conclusion</a></li>
    </ul>
  </nav>
      

Return to top of page

Write Search Functions in a Lisp Buffer

Emacs Lisp functions only work within “balanced parentheses”. For this reason, it proved necessary to write my h2a.el function in a LISP buffer.

I found that Lisp gets confused by the angle brackets, such as '<', when I attempt to define a search string in a Lisp function written in an HTML buffer.

In that mode, Emacs treats angle brackets as a kind of parenthesis. The editor may insist upon pairing an opening '<' with a closing ')'.

The Lisp interpreter has difficulty sorting-out that syntax. It may compile search code that fails to find what it is looking for. Or the function may halt with an error.

Writing the code in a different buffer, having its mode set to Lisp instead of to HTML, resolves the difficulty.

You can run the script by invoking its name while editing text in an HTML buffer; it is just better not to try writing it in that HTML buffer.

The previous article ranked sed superior to Emacs for preparing and inserting navigation links from <h2> tags. I would call it more of a tie now, after learning at least one way to achieve the result with Lisp instructions.

Both the approach described here and the sed approach described previously involve an extra step to run a separate program to get the job done. With sed there is just a single line of code. Lisp needed 28 lines.

However, both solutions are examples of write-once-use-many-times. Now that I have both, convenience becomes a consideration. I would say that Emacs moves somewhat ahead of sed because it can be done while the file remains open for editing.

Return to top of page