Articles

Scoped Keys in DITA 1.3 (Finally!)

Screenshot of first page of article by Leigh White.

Author

Leigh White
DITA SPECIALIST AT IXIASOFT

 

DITA 1.3 introduces scoped keys, which represent a huge improvement over the key functionality introduced in DITA 1.2. In DITA 1.2, a key could have only one definition with a root map. DITA 1.3 allows a key to be redefined at multiple levels within a map, with each definition holding over a specified key space. To understand why scoped keys are such a big step forward, let’s look at a simple (and common) use case that’s impossible using the non-scoped keys of DITA 1.2.

DITA 1.2 Keys

You have a root map that references two submaps, widget.ditamap and gadget.ditamap. Both of these maps in turn reference the topic, get_started.dita. That topic contains a paragraph that includes a reference to a key named module name:

<p>The <ph keyref=”module-name”/> will give you years of satisfaction or your money back!</p>

Both widget.ditamap and gadget.ditamap include definitions of the module-name key. Widget.ditamap defines the key as “Widget”:

<keydef keys=”module-name”> <topicmeta> <keywords> <keyword>Widget</keyword> </keywords> </topicmeta> </keydef> and gadget.ditamap defines the key as “Gadget”: <keydef keys=”module-name”> <topicmeta> <keywords> <keyword>Gadget</keyword> </keywords> </topicmeta> </keydef>

When you generate output from the root map, your expectation is that the module-name key will resolve to “Widget” in the first instance of the get_started.dita topic and to “Gadget” in the second instance of the topic. That’s not what happens, however. The module-name key is resolved to “Widget” in both instances of the topic. In DITA 1.2, it was always the first definition of a key that prevailed throughout the entire root map. What exactly does “first definition” mean? It can mean different things in different contexts. The full explanation is too long to include here. If you want the details, refer to this article published by the OASIS DITA Technical Committee: http://dita.xml.

org/resource/dita-tcfaq-about-keys, which clarifies how a processor should determine which key definition is in use. This limitation of keys in DITA 1.2 was very frustrating for the DITA community because it placed the full power of keys tantalizingly just out of reach. In comes DITA 1.3 scoped keys to save the day!

DITA 1.3 Scoped Keys

Scoped keys enable you to create multiple key spaces within a single root map and define the same key differently in each of those key spaces. A key space can be the entire root map, a submap, a topichead or topicgroup, or even a single topicref. First, let’s look at our previous scenario, but now using scoped keys.

<map> <mapref href=“widget.ditamap” keyscope=“widget”/> <mapref href=“gadget.ditamap” keyscope=“gadget”/> </map>

The first thing you notice is the attribute keyscope on both maprefs. This attribute is new in DITA 1.3 and enables you to define a key space. In the example, the entire widget.ditamap is defined as key space “widget” and the entire gadget.ditamap is defined as the key space “gadget”. (There is no named key space defined on the root map. By definition, the root map always has an implicit key space; it’s not necessary to explicitly declare the key space for the root map and doing so can create the potential for double or conflicting key space definitions. The best policy is simply never to define a key space on a map element and instead always define it on the map reference.) Returning to the example, we still have the module-name key defined in both widget.ditamap and gadget.ditamap. Both of those maps still reference the topic get_started.dita, which still includes the following paragraph:

<p>The <ph keyref=”module-name”/> will give you years of satisfaction or your money back!</p>

You can think of a key space as a “fence.” We have created a fence around widget.ditamap and another one around gadget.ditamap. All keys defined within the widget fence keep their defined values only within the widget fence, and all keys defined within the gadget fence keep their defined values only within the gadget fence. In the example, the module-name key in the first instance of the get_started.dita topic, which occurs inside the widget fence (widget.ditamap), resolves to “Widget.” The module-name key in the second instance of the get_started.dita topic, which occurs inside the gadget fence (gadget.ditamap), resolves to “Gadget.”

The result is exactly what we wanted but could not achieve in DITA 1.2. It’s not necessary to have separate maps to create multiple key spaces. As mentioned, a key space can be more granular—a topichead, topicgroup, or even a single topicref. Consider this example:

<map> <title>Training Courses</title> <topicgroup keyscope=”widget”> <topicref href=”get_started.dita”/> </topicgroup> <topichead keyscope=”gadget”navtitle=”Using the Gadget”> <topicref href=”get_started.dita”/> </topichead> </map>

Instead of the two instances of get_started.dita occurring in separate sub-maps, one occurs in a topicgroup and another in a topichead. (Yes it’s a bit  improbable, but it will do for the sake of a simple example!) In this case, the result is the same as in the first scenario—the module-name key in the first instance of the get_started.dita topic resolves to “Widget.” The module-name key in the second instance of the get_started.dita topic resolves to “Gadget.” Finally, look at the following example:

<map> <title>Training Courses</title> <topicref href=”get_started.dita” keysco pe=”widget”product=”widget”> <keydef keys=”module-name”/> (Widget) </topicref> <topicref href=”get_started.dita” keysco pe=”gadget”product=”gadget”> <keydef keys=”module-name”/> (Gadget) </topicref> </map>

Here, we have two topicrefs to the get_started.dita topic in a single map. Each topicref contains its own definition for the module-name key, and each topic is its own key space, meaning, again, that the modulename key in the first instance of the get_started.dita topic resolves to “Widget” And the module-name key in the second instance of the get_started.dita topic resolves to “Gadget.” In these scenarios, it’s clear that the output processor must generate two copies of get_started.dita in order to resolve the key two different ways.

This requirement is stated in the DITA 1.3 specification. The processor will likely name the copies based on its own algorithm (the specification does not outline requirements for this aspect of processing). If you need specific URIs for the copies, you can use the copy-to attribute to specify an appropriate URI for each reference to a topic. The takeaways at this point are that you can use the new keyscope attribute to define a key space, which is in effect a “fence” around a certain set of content within a map. A key defined within a key space can only be referenced by its (unqualified) key name from within that same key space.

No other content set can use the key definitions found within the scoped content set unless explicitly directed to do so via another new mechanism, which we will look at in a moment.

Parallel Key Spaces vs. Nested Key Spaces

When understanding how scoped keys work, it’s important to make a distinction between parallel key spaces and nested key spaces. Parallel key spaces are those that don’t overlap—where one key space ends, another key space begins. The examples we have looked at so far in this article all represent parallel key spaces.

The key spaces are completely sequential and separate. In parallel key spaces, the resolution of keys is intuitive because there is a clear boundary between them. On the other hand, the resolution of keys in nested key spaces is not as intuitive. Nested key spaces, as their name suggests, are key spaces which are defined within other key spaces. Figure 2 shows an example of nested key spaces.

The “Keys3” key space is defined for a topicgroup, which is a child of a map for which the “Keys2” key space is defined, which is in turn a child of a map for which the “Keys1” key space is defined. Would you care to guess how the module-name key would be resolved in this map? If you guessed “Doodad” (based on the key definition with the closest proximity to the key usage), you’re wrong. The key is resolved as “Widget.”

To understand why, keep in mind that with the introduction of scoped keys, it’s necessary to retain complete backwards-compatibility with keys as they functioned in DITA 1.2. Again, in DITA 1.2, the “first” definition of a key within a root map was the one that held throughout the publication.

Many documentation teams have a large body of content designed around this principle. It would be time-consuming to rework all that content to accommodate a different method of key resolution. The architectural complexity of managing nested or overlapping key spaces in heavy reuse situations would also be quite considerable. The main point to remember is that if the same key is defined at both a parent and child level, the parent definition always wins. As a side note, avoiding inadvertent nested key spaces is a very good reason not to define key spaces on root maps.

You might never know when a map that’s the root map for one publication will be used as a sub-map in another publication.

Sharing Keys Between Key Spaces

There are theoretically two approaches to sharing keys between key spaces. The first is to simply allow a key definition at a higher level to be “inherited” at a lower level as a fallback if no lower-level definition is present, as a safeguard against unresolved keys. Following is an example of that approach:

<map> <keydef keys=”version”> (1.1) <mapref href=”widget.ditamap” keyscope=”widget”/> <keydef keys=”module-name”> (Widget) <topicref href=”get_started. dita”/> <mapref href=”gadget.ditamap” keyscope=”gadget”/> <keydef keys=”module-name”> (Gadget) <keydef keys=”version”> (3.6) <topicref href=”get_started. dita”/> </map>

We have a key named version defined as “1.1” at the root map level and as “3.6” at the sub-map level. We also have the module-name key defined as “Widget” in the first sub-map and as “Gadget” in the second sub-map. Both sub-maps reference the get_started.dita topic which contains a paragraph that includes both keys:

<p>The <ph keyref=”module-name”/> <ph keyref=”version”/> will give you years of satisfaction or your money back!</p>

Our intention in adding the version key at the root map level was so that if it was not defined in a sub-map, the definition could “fall back” to the one at the root level so we won’t be left with an unresolved key. Following that intention, we expect the version to resolve to “1.1” in the first instance of get_started.dita (as a fallback) and to “3.6” in the second instance. Can you see why this approach will not work the way we intend? We come back to the principle “if the same key is defined at both a parent and child level, the parent definition always wins.”

Based on that principle, the version key resolves to “1.1” in both instances of the topic. You can’t use a key across key spaces in a nested key space situation. There is, however, a “legit” way to use a key in a key space across key spaces in a parallel key space situation. Let’s look at the same map as in the previous example, but without the version key defined at the root map level:

<map> <mapref href=”widget.ditamap” keyscope=”widget”/> <keydef keys=”module-name”> (Widget) <keydef keys=”version”> (3.6) <topicref href=”get_started. dita”/> <mapref href=”gadget.ditamap” keyscope=”gadget”/> <keydef keys=”module-name”> (Gadget) <topicref href=”get_started. dita”/> </map>

Again, the get_started.dita topic includes this paragraph:

<p>The <ph keyref=”module-name”/> <ph keyref=”version”/> will give you years of satisfaction or your money back!</p>

In this case, the version key will be unresolved in the second instance of the topic because the “gadget” key space does not define the version key. It will be resolved in the first instance of the topic because the “widget” key space does define the version key. Now, you could always define the version key in the gadget key space as well, but let’s say you don’t have access to the root map to add the key within the gadget.ditamap mapref, or you know that the value should be the same for both Widget and Gadget, and you don’t want to be redundant. In this case, because you have parallel key spaces, you can reuse the definition of the widget version key in the “widget” key space.

To do so, you include the name of the key space in the keyref attribute: <p>The <ph keyref=”module-name”/> <ph keyref=”widget.version”/> will give you years of satisfaction or your money back!</p>
Because the name of the key space, “widget,” is in the key reference, a compliant processor can locate that key space and within it, locate the referenced key. It’s analogous to including the path in a link to ensure the link can locate the referenced file. And, just as a file path can be relative or full, the path to a key space can also be relative or full. Consider this example:

<map> <mapref keyscope=”widget”> <topichead keyscope=”gadget”> <topicgroup keyscope=”doodad”> <keydef keys=”trim”> (Pro) </topicgroup> </topichead> </mapref> <mapref> <topicref href=”get-started. dita”/> </mapref> </map>

In this (yes, also improbable) example, the root map references two sub-maps. The “widget” key space is defined for the first sub-map. Within that map, a nested “gadget” key space is defined on the topichead and within the topichead, another nested key space, “doodad,” is defined on the topicgroup. Within the “doodad” key space, the trim key is defined. Within the second sub-map, the topic get_started.dita includes a paragraph that references the trim key:

<p>The <keyword keyref=”widget.gadget. doodad.trim”/> line of tools offers the best value.</p>

Notice that the full path to the “doodad” key space is included in the key reference. It is not enough to include “doodad.trim” as the keyref because the “doodad” key space is not at the highest level in the map; it is nested within other key spaces. You must start at the shared parent level and navigate down to the correct key space.

Cross-Deliverable Linking with Scoped Keys

DITA content creators and Information Architects have long searched for a mechanism to reliably create links from one publication to another. For example, say a company uses two maps to create two independent help systems. There are numerous occasions when it makes sense to link from a topic in Help System One to a topic in Help System Two. Many groups have created home-grown solutions to this need, but there has been no out-of-the-box mechanism in DITA for it.

Scoped keys introduces that mechanism. Take a look at this simple scenario:

<map> <title>Widget Online Help</title> <mapref href=”gadget.ditamap” scope=”peer” keyscope=”gadget”/> <topicref href=”get-started_widget. dita”/> </map>

The widget.ditamap is used to create a help system for the Widget product. It includes a number of topicref elements, including one to get-started_widget.dita. It also includes a reference to gadget.ditamap, which is the map used to create the help system for the Gadget module. Notice that gadget.ditamap is referenced as a peer map and that it defines a key space for itself, “gadget.” The gadget.ditamap also includes a number of topicref elements, as well as an integ-gadget key defined as the topic integrating-gadget.dita:

<map> <title>Gadget Online Help</title> <keydef keys=”integ-gadget” href=”integrating-gadget.dita”/> </map>

The final piece of this example is the topic get-started-widget.dita. In this topic, we want to include a link to the integrating-gadget.dita topic, which we do by including a reference to the integ-gadget key:

<concept id=”getstartwidget”> <title>Getting started with the Widget</title> <conbody> <p>If you choose to <xref keyref=”gadget.integ-gadget”>integrate the Gadget module</xref>, many more options will be available to you.</p> </conbody> </concept>

Because this key definition is available to integrating-gadget.dita by virtue of having been defined in gadget.ditamap, which in turn names the “gadget” keyspace, a processor can resolve the xref as <xref href=”[some path]/integratinggadget.dita”/> and perhaps ultimately as an active hyperlink. We say “perhaps” because the DITA 1.3 specification does not outline how a processor should implement the generation of crossdeliverable links.

Logically, a processor would create active links but that implementation is entirely up to the developers of the DITA Open Toolkit and other tools vendors. The DITA 1.3 specification merely outlines the basic mechanism for setting up the potential for crossdeliverable linking.

In summary, scoped keys in DITA 1.3 fill a need that has been present since keys were first introduced in DITA 1.2—they allow for a key to have more than one definition within a root map, thus greatly increasing the usability and flexibility of keys.

This article is a brief overview of scoped keys. For a more in-depth look, refer to the OASIS Technical Committee’s DITA 1.3 Feature Article, Understanding Scoped Keys in DITA 1.3 at https://www.oasis-open.org/committees/ download.php/56472/Understanding%20 Scoped%20Keys%20In%20DITA%201.3.pdf. The feature article offers additional examples and use cases and discusses some of the reuse implications for scoped keys.