We’ve talked about doing run-time conditional processing in our help for a very long time, nearly as long as I’ve been around. When you have applications that can run in multiple environments, you sometimes need to write different help text for difference cases.

For example, take an instant messaging application like Empathy. In GNOME 3, the Telepathy integration means that users can chat directly from the tray. Hackers know it’s not actually Empathy, but users don’t care. It’s chat. In GNOME 2, you usually interact with Empathy through the notification tray. In Unity, it integrates with the messaging menu. These are all things we should write about in the help.

But it’s hard to write about things when you don’t know where your help is read. The only way to deal with this is to patch the help for different environments. My experience is that that rarely happens. Plus, you don’t always create different packages for different environments. The exact same Empathy package in Fedora is used in GNOME Shell, GNOME fallback, and XFCE.

Other XML formats have conditional processing, but their design isn’t suitable for our needs. They usually work by having a set of attributes that take some tags. The behavior isn’t usually specified, because it’s assumed that they’ll be handled by a vendor-specific build tool that strips things out before creating a deliverable. Our source format is our deliverable format, and we want to adapt it at run-time.

So I’ve just landed some changes in Yelp to support a Mallard extension format for run-time conditional processing. Any block element can have the if:test conditional attribute added. This is evaluated as an XPath expression with extension functions that can tell you things about the environment. This is much more flexible than a set of tagging attributes, because you can combine multiple tests with “and” and “or“.

<p if:test="if:env('gnome-shell')">This is only shown in GNOME Shell</p>
<p if:test="if:env('gnome-shell') or if:env('gnome-panel')">
  This is shown under GNOME Shell or GNOME Panel
</p>

The if:test attribute is a convenient short-hand syntax. There are also conditional processing elements that allow you to do basic branching and fallback.

<if:choose>
  <if:if test="if:env('unity')">
    <p>You're using Unity</p>
  </if>
  <if:if test="if:env('xfce')">
    <p>You're using XFCE</p>
  </if:if>
  <if:else>
    <p>You're using something else</p>
  </if:else>
</if:choose>

The full syntax has another advantage. What happens if you hand a document with conditional processing to a Mallard processor that doesn’t support it? It turns out that Mallard has very well-defined rules for how unknown extensions are handled. In the first case, you just have an external attribute on a known element. The attribute is ignored and the p element is processed as normal.

In the second case, you have an external element in a block context. A Mallard implementation that doesn’t do conditional processing will visit the children in restricted block context. In restricted block context, unknown external elements are ignored.

Yelp 3.0 doesn’t have the conditional processing support. The following two snippets will do what they say in Yelp 3.0, but they will be exactly equivalent to Yelp 3.2 with conditional processing support.

<p if:test="if:env('foo')">
  This will be displayed in Yelp 3.0
</p>
<if:choose>
  <if:if test="if:env('foo')">
    <p>This will not be displayed in Yelp 3.0</p>
  </if:test>
</if:choose>

Details are all subject to change, of course. This does basically follow a proposal I made to the Mallard mailing list last August. But for now, it’s in an experimental namespace. I just need to iron out the kinks and write up a specification on projectmallard.org.

Open Help Conference