Proposal to provide a mechanism for different effective key definitions at different locations within a map structure.
Future post DITA 1.2 work from Issue #12007: Item 2: Consider adding a scoping mechanism for use with key definitions and possibly other aspects of DITA based on the map hierarchy or the combined element hierarchy within a set of maps. Related topic: Consider allowing subsequent key definitions within the same map or within peer maps to override previous key definitions, probably as part of some hierarchal scope, and probably in a fashion that is the same as or similar to override mechanisms to be developed for other features such as ditaval.
Omnibus publications that combine multiple standalone maps, each with their own set of key definitions.
Cases where a topic is reused at multiple locations in a map structure, but the behavior of links and/or text replacement for each use context must be different.
In DITA 1.2, a given root map in a map structure defines a single key space, with exactly one effective definition for every key name. This poposal would allow a map author to place a virtual "fence" around a section of the map, and say, essentially, "keys defined in this section of the map structure, stay in (can only be referenced from) this section of the map structure." Thus for a given key, there could be different effective definitions at different locations - inside different "fences" - within the map structure. These "fences" are called key scopes, and they are defined by putting a @keyscope attribute on a map or topicref element. Key definitions within a scope are called scoped key definitions.
There are cases where a topic or map in one scope needs to reference a piece of content from a different scope. For example, an omnibus piece of technical documentation where each section applies to a single component of the product, and is mostly self-contained, but occasionally needs to link to something in another section. For this reason, the @keyscope attribute specifies a name that can be prepended to key references to address keys within that scope. For example, you could reference a key "k" in a scope "s" from outside that scope via the key reference, "s.k". This is called a scope-qualified key name. If a given definition is within multiple layers of scopes - a fence within a fence - you would specify the full "scope path" to the key, e.g. keyref="outerScope.innerScope.keyName".
The detailed requirements for key reference processing in a map containing key scopes are as follows.
The content considered to be part of a key scope is as follows:
The key scope-defining element itself. This means that any key definitions specified by the element are part of the scope, and any key reference on the element is evaluated against the effective key definitions for the scope.
The key scope-defining element’s contents after conref resolution.
Any locally-scoped content referenced from within the key scope via @href on a topicref (or specialization).
The root element of the root map in a map structure always defines an implicit root key scope. There are no other implicit key scopes. For example, no key scope is introduced by referencing a child DITA map; the content brought in by such a reference is part of the same scope as the map reference.
The value of the @keyscope attribute is used when generating implicit scope-qualified key names for the parent scope. As such, it must also be a legal key name. Scope names should be unique within a given parent scope. If there are duplicates, conflicting qualified key names may - and likely will - result. However, unqualified references within those scopes will function as expected, resolving to the definition in the local scope. For purposes of implicit qualified key names, the first key scope within a given parent scope with a given name takes precedence.
This change is fully backwards-compatible with DITA 1.2. Existing maps will continue to function exactly as before, since they do not contain any @keyscope attributes.
It will now be possible to combine standalone maps with their own set of key definitions intact.
It will now be possible for map authors to specify context-dependent link behavior and text insertion for topics and sub-maps.
This feature allows for use cases which would not otherwise be possible.
keyscope
New NMTOKEN attribute for topicref-atts group and its variants.
Processors will be required to process key definitions and key references within elements marked with this attribute according to the behavior described above.
Does not contain translatable text.
The cost to updating and maintaining the changes to the DTD and schema files for this change is trivial.
The spec topic “Overview of keys” would need to be extensively updated to discuss key scopes and their implications. I suggest that this topic be split into two topics, “Key Definitions,” and “Determining the Effective Key Definition for a Key Reference.”
The topicref-atts topic would obviously need to be updated, and a new topic created for the @keyscope attribute.
DITA processing applications will have to update key reference processing.
The algorithm and data structures used to construct key spaces will need to be updated to account for key scopes.
Key reference resolution algorithms will need to be made aware of the effective context that applies for each key reference.
Tools that provide keyref-aware auditing and reporting tools will need to be updated to take key scopes into account.
Authoring tools may need to be updated to allow authors to specify the effective scope within a map structure to use to resolve key references during authoring.
For users who already understand the key reference mechanism, this change should be relatively straightforward. That said, the key reference mechanism is already very complex and not well understood, and this proposal does add to that complexity.
This map combines three standalone course maps, each with its own set of keys. Each of the mapref elements is marked with a keyscope attribute, meaning that the key definitions within the referenced map sub-structures will be confined to those sub-structures.
Since the summary key is defined in this map as part of the implicit root scope, and keys defined in parent scopes override those in child scopes, its definition overrides any conflicting definition of summary in any of the referenced maps.
<map xml:lang="en"> <title>Training Courses</title> <mapref href="course-1.ditamap" keyscope="course-1"/> <mapref href="course-2.ditamap" keyscope="course-2"/> <mapref href="course-3.ditamap" keyscope="course-3"/> <topicref keys="summary" href="omnibus-summary.dita"/> </map>
Without key scopes, the key names that have definitions for all three maps, like introduction or prerequisites, would take their effective definition from course-1.ditamap, resulting in incorrect behavior in the output.
In this example, the “oil change” procedures for Tractor X and Tractor Y are similar enough to reuse the same topic for both, but the name of the product is included in the title via keyref. In the map below, the topic common/oilchange.dita is referenced twice. Each reference is wrapped within a topicgroup that defines a key scope, and each scope has its own definition of the ProductName key.
<map xml:lang="en"> <title>Equipment Oil Change Procedures</title> <topicgroup keyscope="Tractor-X"> <keydef keys="ProductName"> <topicmeta> <linktext>Tractor X</linktext> </topicmeta> </keydef> <topicref href="common/oilchange.dita"/> </topicgroup> <topicgroup keyscope="Tractor-Y"> <keydef keys="ProductName"> <topicmeta> <linktext>Tractor Y</linktext> </topicmeta> </keydef> <topicref href="common/oilchange.dita"/> </topicgroup> </map>
<task id="oilchange" xml:lang="en"> <title>Changing the Oil on the <ph keyref="ProductName"/></title> <!-- etc. --> </task>
An alternative way to code the above map would be to specify @keyscope on each of the topicref elements, and next the keydef elements within them. This is a somewhat less intuitive but equally valid formulation.
<map xml:lang="en"> <title>Equipment Oil Change Procedures</title> <topicref href="common/oilchange.dita" keyscope="Tractor-X"> <keydef keys="ProductName"> <topicmeta> <linktext>Tractor X</linktext> </topicmeta> </keydef> </topicref> <topicref href="common/oilchange.dita" keyscope="Tractor-Y"> <keydef keys="ProductName"> <topicmeta> <linktext>Tractor Y</linktext> </topicmeta> </keydef> </topicref> </map>
Without key scopes, the effective text for both references would be “Tractor X,” making this an impossible scenario in DITA 1.2.
This example illustrates how key definitions within a scope do not apply outside that scope. Here, key-1 is defined within the key scope scope-1. Since the reference to key-1 is outside that scope, the reference is treated as an undefined key.
<map xml:lang="en"> <title>Key Definition Containment Example</title> <topicgroup keyscope="scope-1"> <keydef keys="key-1" href="someTopic.dita" /> </topicgroup> <!-- Processors should issue a warning for an undefined key since the reference occurs in the root scope, and the root scope does not contain a definition for key-1. --> <topicref keyref="key-1" /> <!-- However, this reference is OK. --> <topicref keyref="scope-1.key-1" /> </map>
Take the following map:
<map> <topicgroup keyscope="scope-1"> <topicref keys="scope-1-key-1" href="Topic1.dita"/> </topicgroup> <topicgroup keyscope="scope-2"> <topicref keys="scope-2-key-1" href="Topic2.dita"/> </topicgroup> </map>
This map contains two scopes, each with one topic. If you wanted to author a link from Topic 1 to Topic 2, you could do so by creating a reference to the qualified key for Topic 2.
<p>Link to <xref keyref="scope-2.scope-2-key-1">Topic 2</xref>.</p>
In the following map, the mapref specifies a keyscope, and then contains several key definitions. Since key precedence in a scope follows DITA 1.2 rules, the key definitions in this map take precedence over key definitions in the sub-map, and earlier key definitions within the scope take precedence over later ones.
<map id="map-1" xml:lang="en"> <title>Outer Map</title> <topicref format="ditamap" href="submap.ditamap" keyscope="scope-1"> <keydef id="keydef-1" keys="key-1"/> <keydef id="keydef-2" keys="key-2"/> </topicref> <keydef id="keydef-3" keys="key-1"/> </map>
<map id="submap" xml:lang="en"> <title>Sub-Map</title> <keydef id="keydef-4" keys="key-1"/> <keydef id="keydef-5" keys="key-2"/> </map>
Here are the effective key definitions for the scopes in this map structure.
Implicit root scope
scope-1
If a parent scope explicitly defines a qualified key name that would otherwise be implictly defined by virtue of a child scope, the explicit definition takes precedence.
<map> <title>Explicit Key Definition Example</title> <keydef keys="child.keyName" id="keydef1" /> <topicgroup keyscope="child"> <keydef id="keydef2" keys="keyName" /> <topicref id="topicref1" keyref="keyName" /> <topicref id="topicref2" keyref="child.keyName" /> </topcgroup> <topicref id="topicref3" keyref="keyName" /> <topicref id="topicref4" keyref="child.keyName" /> </map>
The effective definition for the different topicrefs are:
Take child map, child.ditamap:
<map keyscope="mapScopeName1 mapScopeName2"> <keydef keys="key-1" /> </map>
And this parent.ditamap:
<map> <title>Publication Title</title> <mapref keyscope="maprefScopeName1 maprefScopeName2" href="child.ditamap" /> <topicref keyref="mapScopeName1.key-1" /> <topicref keyref="mapScopeName2.key-1" /> <topicref keyref="maprefScopeName1.key-1" /> <topicref keyref="maprefScopeName2.key-1" /> </map>
All references will resolve because the child scope has four names.