RubyFrontier Documentation

Cross-References and Auto-Numbering

Let’s say I want to link from one page in a site to a particular section of another page. To do this, I might supply the heading for the section in the target page with an id attribute, and form my link using the page identifier and the id attribute value. If I want the content of this link to be the text of the heading, I must also supply that text. So, my link might look like this: <a href=otherpage#theID>My Cool Section</a>. This works, but it’s rather fussy, since in the linking page I must know not one but three things about my target: the identifier for the target page, the id for the heading in the target page, and the text of the heading in the target page. Wouldn’t it be nice to know just one thing, the heading id attribute, and to be able to form the whole link from this one piece of information?

What I’m describing here is a cross-reference. Most decent software for constructing a book or a book-like object, such as FrameMaker or AsciiDoc, provides an easy way of making cross-references, and it seems only reasonable that RubyFrontier should do likewise. To help you with this, RubyFrontier provides two standard macros, makexref() and xref(). These macros take advantage of (some might say “misuse”) the autoglossary mechanism to make cross-references possible.

makexref() takes two parameters, an identifier and a hash. It simply pops that hash into the autoglossary, keyed by the identifier. Making sure your identifiers are unique and don’t trample on one another or on any of the automatically generated identifiers for page objects is entirely up to you. The purpose of the hash will be made clear when you read about xref(). The identifier for the current page is automatically added to your hash, under the key :page_id (so don’t use that key for anything).

xref() is effectively the converse of makexref(). It takes three parameters, of which the third is optional:

A little thought will reveal that together, makexref() and xref() provide everything you need to make various types of cross-reference. Perhaps this will be clearer through an example, so here is one.

Section Cross-References

Let’s say that a page has a section with a heading called “About This Page”. If we know we’re going to want to form a cross-reference to that heading, we can set this up by giving that heading an id attribute, and calling makexref() with the value of that id. I’ll cleverly pick an id value that is unlikely to be used elsewhere, so I don’t accidentally tromp on anything in the autoglossary (that’s why my id value starts with SEC). And I’ll cleverly store the content of the heading in the hash, under the symbol name :sectitle.

  <% makexref("SECAboutThisPage", :sectitle => "About This Page") %>
  <h2 id="SECAboutThisPage">About This Page</h2>

Now on some other page, when we want to form a cross-reference to that heading, here’s all we have to do:

  <%= xref "SECAboutThisPage", :sectitle %>

That macro will be turned into the phrase “About This Page”, wrapped up as a link pointing to the heading on the first page!

Once you understand the basic principles, you will undoubtedly be able to think of ways to set up our original makexref() call with less repetition. For example, we are repeating the phrase “About This Page”. This is unnecessary, since we just finished storing it in the autoglossary. So we can reliably fetch it right back out again, like this:

  <% makexref("SECAboutThisPage", :sectitle => "About This Page") %>
  <h2 id="SECAboutThisPage"><%= xref "SECAboutThisPage", :sectitle, false %></h2>

That shows exactly what the third parameter is for. By setting the third parameter to false, we are able to fetch and substitute the section title without forming a link (which would be very silly, since it would be a link to the heading we are already in).

That was good: we eliminated the repetition of “About This Page”. But we are now repeating the id value "aboutthispage", not once but twice. So let’s take an even better approach. We’ll supply a macro to form both the makexref() call and the section heading, all in a single move. Let’s call our macro section(). So I’ll write the macro as a file section.rb in the #tools area of my site. The file looks like this:

  def section(s)
    xref = "SEC" + s.dropNonAlphas
    makexref(xref, :sectitle => s)
    %{<h2 id="#{xref}">#{s}</h2>}

Now we can form our heading with a single macro call, like this:

  <%= section "About This Page" %>

The call to the section() macro forms the id as "SECAboutThisPage", uses it to call makexref(), and returns the <h2> tag with the correct id and the contents “About This Page”, all in a single move. And our other call still works perfectly to form a cross-reference:

  <%= xref "SECAboutThisPage", :sectitle %>

Publish Site (No Preflight)

However, there’s a slight glitch. The cross-reference mechanism depends on the autoglossary. But the autoglossary is completely erased and rebuilt every time you use the RubyFrontier > Publish Site command. This means that if the order of pages is such that a page containing an xref() call is processed before the corresponding makexref() call is processed, the autoglossary won’t yet contain the specified cross-reference hash. You’ll get an error message warning that the cross-reference wasn’t resolved, and the page with the xref() call won’t contain the desired cross-reference.

The solution is simple! Instead of (or, even better, right after) using RubyFrontier > Publish Site, you use RubyFrontier > Publish Site (No Preflight). This is exactly what this command is for! It publishes the site without rebuilding the autoglossary. Instead, it uses the autoglossary that you generated the last time you published the site. A typical approach, in a site with cross-references, is to publish the site twice — first with preflighting, then without. This assures that the autoglossary is correctly built, the first time, and then used to form the cross-references, the second time.


A book will often have numbered or captioned figures and examples, and will often refer to these elsewhere by number or caption. Thus, although auto-numbering is not exactly the same as cross-references, it makes sense to mention it in connection with the cross-reference mechanism.

It’s important to understand that auto-numbering, of itself, does not involve cross-references at all. Auto-numbering, of itself, is easy, and doesn’t require any special action on your part (except that you should write a macro of some sort). For example, you may have noticed that the figures in this document are auto-numbered. All the figures are formed by calling a caption() macro, which forms the nice box around the picture and the caption, and incidentally also numbers the figure:

  @adrPageTable[:fignum] ||= 0
  @adrPageTable[:fignum] += 1
  fignum = "Figure " + @adrPageTable[:fignum].to_s

All we’re doing here is using the page table as global storage, making sure we restart figure numbering for each rendered page, and incrementing the current number every time we encounter the caption() call. So, no cross-referencing is needed merely in order to label a figure with a number.

But as soon as you want to refer to a figure by its number, now you need cross-referencing. In my sites that use references to figures, my macro that forms and auto-numbers a figure also stores the number in the autoglossary by calling makexref(), under some reasonable identifier, such as "FIGmyCoolFigure". And of course the macro also uses this identifier as the id attribute of the figure’s caption. So now it is trivial to form a reference elsewhere to this figure, a reference that uses the figure’s number, by calling xref() with the same identifier. This works right within the same page, as well as in other pages. And the reference doesn’t have to be a link; I can call xref() with the third parameter set to false.

For example, here’s a picture of me:

Figure 1: A picture of me

Now I can refer to that picture as Figure 1, even if I don’t want to form a link. The words “Figure 1” were not typed manually; rather, they are the result of a macro call:

  <%= xref "FIGmoi", :fignum, false %>

Thus, references to figures will keep working even if more figures are added or the figures are rearranged, and the auto-numbering changes. And of course if I do want the reference to be a link, it can be: See Figure 1. I use this device on various pages in this documentation, such as here.

Next: User Settings

This documentation prepared by Matt Neuburg, phd = matt at tidbits dot com (, using RubyFrontier.
Download RubyFrontier from GitHub.