Conditional Processing

Learn by Example

This page was written in a Mallard extension format called Mallard Sites. Read the source markup for this page to learn Mallard from real-world examples.

Writers often need to document software in different environments or on different platforms, reusing as much common content as possible. Additionally, some content is only suitable for certain reading targets (such as print or online) or certain languages, and occasionally it's useful to use different content based on which Mallard features or extensions are supported.

Mallard provides a powerful conditional processing system that allows you to mark blocks with conditions. In addition to simple conditions, you can also write a set of conditional branches such that only one will be used, and you can combine conditions in logical expressions.

Basic Conditionals

The simplest way to use conditionals is to use the if:test attribute on any block element, such as on the p element for a conditional paragraph. Because conditional processing is a Mallard extension, the if:test attribute is in a separate namespace. Declare this namespace on the top-level page element.

<page xmlns="http://projectmallard.org/1.0/"
      xmlns:if="http://projectmallard.org/if/1.0/"
      type="topic" version="1.0 if/1.0"
      id="THE_PAGE_ID">

All the examples throughout this page assume you've added added the xmlns:if declaration as above. Additionally, this example shows the version attribute set to "1.0 if/1.0". This isn't necessary to use conditional processing, but it will make validation better with tools that support Mallard schema merging.

Conditionals use test tokens, which consist of a token scheme and a token value separated by a colon. For example, to test if the user is running GNOME 3 when reading the help with the Yelp help viewer, use the test token platform:gnome-shell.

<p if:test="platform:gnome-shell">This content is only shown when
running under GNOME 3.</p>

This content is only shown when running under GNOME 3.

Token schemes help group related types of conditions. Some other languages with conditional processing have separate test attributes for different types of conditions. Mallard uses a single attribute, but uses test tokens to identify the type of test. This allows conditions to be combined in powerful ways.

Tokens that test something about the environment the user is getting help for use the platform: scheme. This could test for the operating system, the desktop environment, the computer architecture, or even the user's default applications or other preferences.

Tokens that test something about the environment where the user is reading the help use the target: scheme. This can be used to provide different content in online and print editions of the help, to provide extra information for assistive technologies like screen readers and braille displays, or to create responsive content for mobile devices.

To test if the help is being read as an HTML page, use the target:html token.

<p if:test="target:html">This content is only shown when viewing
the help as an HTML page.</p>

This content is only shown when viewing the help as an HTML page.

To test if the help is being read on a mobile device, use the target:mobile token.

<p if:test="target:mobile">This content is only shown when viewing
the help on a mobile device.</p>

This content is only shown when viewing the help on a mobile device.

(When viewing this tutorial online, the above example is responsive and will show the output only on mobile devices. Try it with your phone.)

List Items and Other Blocks

You can use the if:test attribute on any other block elements exactly as you would with paragraphs. For example, use the if:test attribute on the media element to add an image that's only shown in HTML output.

<media type="image" src="mallard-logo.png" if:test="target:html"/>

You can also use the if:test attribute on any item element in a list, steps, terms, or tree element. For example, use if:test to make one step in a procedure conditional.

<steps>
<item if:test="platform:windows"><p>Download and install an SSH
client for Windows.<p></item>
<item><p>SSH into <sys>example.com</sys>.</p></item>
<item><p>Run some commands.</p></item>
</steps>
  1. SSH into example.com.

  2. Run some commands.

But be careful, you can only use the if:test attribute on general block elements and list items. You cannot currently use it on table rows or columns, title or desc elements, inline elements, sections, or entire pages. Unlike other languages, Mallard conditionals do not assume a pre-processing step. They are designed to be evaluated at run-time, and are somewhat constrained by the extension mechanisms that Mallard provides.

Branching and Fallback

The simplest way to use conditionals is to just use the if:test attributes, but you can also create multiple conditional branches using the if:choose, if:when, and if:else elements. You can use an if:choose element in the same way and places that you can use any other block element. The if:choose element can contain one or more if:when elements, each of which has a test attribute that's evaluated the same as the if:test attribute on block elements. The first if:when element that is true is used, and the remainder are ignored.

For example, use if:choose and if:when to create two different paragraphs for GNOME 3 and Unity.

<if:choose>
  <if:when test="platform:gnome-shell">
    <p>This is shown in GNOME 3.</p>
  </if:when>
  <if:when test="platform:unity">
    <p>This is shown in Unity.</p>
  </if:when>
</if:choose>

This is shown in GNOME 3.

Because these conditions are mutually exclusive, this example could have just as easily been done using two p elements with if:test attributes. There are, however, advantages to using if:choose. If you have a group of elements you want to make conditional, putting them in a single if:when keeps you from having to write the same conditional multiple times. Also, grouping the content makes it easier for you to check that all conditional content has material for all the conditions you intend to support.

Another advantage to if:choose is that it lets you provide content that's used when none of the other content is used, even if you can't test for it explicitly. Use the if:else element after all if:when elements to provide fallback content.

<if:choose>
  <if:when test="platform:gnome-shell">
    <p>This is shown in GNOME 3.</p>
  </if:when>
  <if:when test="platform:unity">
    <p>This is shown in Unity.</p>
  </if:when>
  <if:else>
    <p>This is shown under any other environment.</p>
  </if:else>
</if:choose>

This is shown in GNOME 3.

You can also just put the fallback content directly at the end of the if:choose element, without an if:else element.

<if:choose>
  <if:when test="platform:gnome-shell">
    <p>This is shown in GNOME 3.</p>
  </if:when>
  <if:when test="platform:unity">
    <p>This is shown in Unity.</p>
  </if:when>
  <p>This is shown under any other environment.</p>
</if:choose>

This is shown in GNOME 3.

There is a subtle difference between these two examples. They will behave exactly the same in any tool that supports conditionals, but Mallard does not require tools to support extensions like conditionals. Because of the way Mallard defines fallback behavior for unsupported extension content, the first example will show nothing in an unsupporting tool, and the second will shown the fallback content. For more details, see Fallback Behavior in the conditionals specification.

You can only use the if:choose element for general block content. There is currently no way to do branching on list items.

Advanced Test Conditions

Until this point, all the conditionals have tested for a single token to be set. You can combine tokens in logical expressions to test for tokens to be not set, for all of a set of tokens to be set, or for any of a set of tokens to be set.

To test for a token to be not set, put an exclamation point in front of it.

<media type="image" src="mallard-logo.png" if:test="!target:mobile"/>

(When viewing this tutorial online, the above example is responsive and will not show the image on mobile devices. Try it with your phone.)

To test for a set of tokens to all be set, put them all into the condition separated by spaces.

<p if:test="platform:gnome-shell platform:fedora">This is only shown
when running GNOME 3 on Fedora.</p>

This is only shown when running GNOME 3 on Fedora.

You can combine these two to test for any combination of tokens being set and not set. For example, the target:html token is set for any serialization of HTML, while the target:xhtml token is only defined for the XML serialization. Combine them to test for non-XML HTML.

<p if:test="target:html !target:xhtml">This is only shown when
converted to HTML that is not XHTML.</p>

This is only shown when converted to HTML that is not XHTML.

You can also test if any of a group of conditions is met by putting them all into a condition separated by commas.

<p if:test="platform:gnome-shell, platform:unity">This is in both
GNOME 3 and Unity, but not other environments.</p>

This is in both GNOME 3 and Unity, but not other environments.

You can combine all three of these techniques. There is no grouping of logical expressions, however. Conditions are first split on commas, then on spaces, and then negations are considered. Sometimes this means you have to write longer conditionals, but it's always possible to put any logical expression into this form.

For example, say you want to test that the user is running GNOME 3, but only if the user is running Fedora or Ubuntu. In an expression with grouping, you might write something like "gnome-shell AND (fedora OR ubuntu)". Instead, you must split this up and write the gnome-shell condition twice, as "(gnome-shell AND fedora) OR (gnome-shell AND ubuntu)".

<p if:test="platform:gnome-shell platform:fedora,
            platform:gnome-shell platform:ubuntu">This is shown under
GNOME 3, but only on Fedora or Ubuntu.</p>

This is shown under GNOME 3, but only on Fedora or Ubuntu.

You can find more advanced conditional examples in the conditionals specification.