As I mentioned during this morning's TC call, Tom Magliery reached out to me last week with some questions from his team on some of the nuances of scoped keys. I've restructured things so that the questions and the answers are together, and put it in the attached text file (when I paste it into Outlook, I lose the indenting). Chris Scoped Keys ----------- Some notes on the format I'Ve used to describe key scope structures below: * An entry with a leading dash denotes a key scope boundary. * An entry in parentheses is an implicit qualified key derived from a child scope. * An unadorned line is an explicit key definition. * Overridden key definitions are omitted. * Comments are wrapped in square brackets. ----------------------------------------------------------- <!-- Sample - 1 - What are the effective keys in ks_0 in map-0.ditamap? --> <!-- map-0.ditamap --> <map> <topicgroup keyscope="ks_0"> <keydef keys="key_a" href="a.dita" /> <keydef keys="key_b" href="b.dita" /> </topicgroup> <mapref href="map-1.ditamap" /> <topicgroup keyscope="ks_0"> <keydef keys="key_a" href="a.dita" /> <keydef keys="key_c" href="c.dita" /> </topicgroup> </map> <!-- map-1.ditamap --> <map> <topicgroup keyscope="ks_0"> <keydef keys="key_b" href="b.dita" /> <keydef keys="key_c" href="c.dita" /> </topicgroup> </map> There is no such thing as a non-contiguous key scope. The names only come into play when determining the qualified key names the parent scope can use to address the keys in that scope. Here, we have three completely distinct key scopes which just happen to share the name ks_0. I'Ll refer to them as ks_0a (the first one in map-0), ks_0b (the second one in map-0), and ks_0c (the one in map-1). The root map also introduces an implicit scope, so there are four scopes in all. The keys key_a, key_b, and key_c are very straightforward to determine for all three non-root scopes. - ks_0a key_a=a.dita key_b=b.dita - ks_0b key_a=a.dita key_c=c.dita - ks_0c key_b=b.dita key_c=c.dita The unqualified key names are scoped - fenced off - in their respective scopes. Determining the qualified key bindings requires a little more thought, but not much. Because these three scopes share the same name, a level of ambiguity is introduced. Here's how to solve the ambiguation. A key space is determined via a breadth-first traversal of the map tree within a scope (and the root map introduces the 'root scope', so we're really talking about four scopes in all, here). A breadth-first traversal of the root map structure would encounter ks_0a first, then ks_0b, then ks_0c. For purposes of precedence, the qualified key definitions provided by a scope boundary are considered to occur at that scope boundary. So: ks_0.key_a=a.dita (from ks_0a) ks_0.key_b=b.dita (from ks_0a) ks_0.key_c=c.dita (from ks_0b) ks_0a doesn't contain key_c, so no ks_0.key_c is contributed to the root scope for ks_0a. The definition for ks_0.key_a provided by ks_0b takes lower precedence than the one provided by ks_0a, because ks_0a occurs first. However, this is the first definition for key_c, so ks_0.key_c is provided by ks_0b. Next, the breadth-first traversal encounters ks_0c. Since all of its qualified key definitions have been provided by the other two, shallower scopes, none of its qualified definitions take precedence. However, if ks_0c introduced key_d, then that would be contributed to the root scope as ks_0.key_d. Finally, since a child scope inherits all of the key definitions from its parent scope, including qualified key names provided by itself and its sibling scopes, all thre ks_0 instances inherit those three qualified definitions. So the final tally is: - Root scope (ks_0.key_a=a.dita) (from ks_0a) (ks_0.key_b=b.dita) (from ks_0a) (ks_0.key_c=c.dita) (from ks_0b) - ks_0a [all keys from root] key_a=a.dita (local definition) key_b=b.dita (local definition) - ks_0b [all keys from root] key_a=a.dita (local definition) key_c=c.dita (local definition) - ks_0c [all keys from root] key_b=b.dita (local definition) key_c=c.dita (local definition) ----------------------------------------------------------- <!-- Sample - 2 - Is the key scope hierarchy valid? - What are the <xref> targets in t_000.dita? - What are the <xref> targets in t_00.dita? - What are the <xref> targets in t_0.dita? --> <map> <topicgroup keyscope="ks_0"> <keydef keys="key_a" href="a.dita" /> <topicgroup keyscope="ks_0"> <keydef keys="key_a" href="aa.dita" /> <topicref href="t_00.dita"/> <topicgroup keyscope="ks_0"> <keydef keys="key_a" href="aaa.dita" /> <topicref href="t_000.dita"/> </topicgroup> </topicgroup> </topicgroup> <topicref href="t_0.dita"/> </map> <!-- t_000.dita --> <task> <xref keyref="key_a"/> <xref keyref="ks_0.key_a"/> <xref keyref="ks_0.ks_0.key_a"/> <xref keyref="ks_0.ks_0.ks_0.key_a"/> </task> <!-- t_00.dita --> <task> <xref keyref="key_a"/> <xref keyref="ks_0.key_a"/> <xref keyref="ks_0.ks_0.key_a"/> <xref keyref="ks_0.ks_0.ks_0.key_a"/> </task> <!-- t_0.dita --> <task> <xref keyref="key_a"/> <xref keyref="ks_0.key_a"/> <xref keyref="ks_0.ks_0.key_a"/> <xref keyref="ks_0.ks_0.ks_0.key_a"/> </task> Again, a scope inherits all key definitions from its parent scope. And again, we have different scopes with the same name, but instead of being siblings, they're nested inside one another. The key space structure is: - Root scope (ks_0.key_a=a.dita) (ks_0.ks_0.key_a=aa.dita) (ks_0.ks_0.ks_0.key_a=aaa.dita) - ks_0 [all keys from the root scope; ks_0.key_a and ks_0.ks_0.key_a overridden] key_a=a.dita - ks_0 [all keys from parent; key_a and ks_0.key_a overridden] - ks_0 [all keys from parent; key_a overridden] Thus, t_00.dita and t_000.dita, occurring in ks_0.ks_0 and ks_0.ks_0.ks_0 respectively, have the same effective key definitions: <task> <xref keyref="key_a" href="a.dita"/> <xref keyref="ks_0.key_a" href="a.dita"/> <xref keyref="ks_0.ks_0.key_a" href="aa.dita"/> <xref keyref="ks_0.ks_0.ks_0.key_a" href="aaa.dita"/> </task> However, t_0.dita is at the root scope, which has no definition for an unqualified key_a, so: <task> <xref keyref="key_a"/> <!-- no definition --> <xref keyref="ks_0.key_a" href="a.dita"/> <xref keyref="ks_0.ks_0.key_a" href="aa.dita"/> <xref keyref="ks_0.ks_0.ks_0.key_a" href="aaa.dita"/> </task> (Side note: one potential algorithm is to implement a key scope such that it delegates all resolution requests to its parent, and only consults its local definitions if the parent's resolution attempt fails or if there is no parent, i.e. the root scope. This is the algorithm I've been using when I test out these concepts.) ----------------------------------------------------------- <!-- Sample - 3 - What is the <xref> target in t_000.dita? --> <map> <topicgroup keyscope="ks_0"> <keydef keys="key_a" href="aa.dita" /> <topicgroup keyscope="ks_1"> <keydef keys="key_a" href="aaa.dita" /> <topicref href="t_000.dita"/> </topicgroup> </topicgroup> <topicref href="t_0.dita"/> <keydef keys="key_a" href="a.dita" /> </map> <!-- t_000.dita --> <task> <xref keyref="ks_0.key_a"/> </task> The root scope gets its definition for ks_0.key_a via the key_a definition in scope ks_0. This definition is inherited by all child, grandchild, etc. scopes. t_000.dita occurs in scope ks_0.ks_1, which descends from the root scope, so the effective definition for ks_0.key_a is aa.dita. For completeness, here's the scope structure: - Root scope key_a=a.dita (ks_0.key_a=aa.dita) (ks_0.ks_1.key_a=aaa.dita) - ks_0 [all from root; key_a overridden] (ks_1.key_a=aaa.dita) - ks_1 [all from ks_0; key_a overridden] ----------------------------------------------------------- <!-- Sample - 4 - What does the key scope hierarchy look like in map-0.ditamap? - How to reference key_a and key_b explicitly in t_0.dita? (i.e., use dot notation). --> <!-- map-0.ditamap --> <map> <mapref href="map-1.ditamap" keyscope="ks_0"/> <topicref href="t_0.dita"/> </map> <!-- map-1.ditamap --> <map> <topicgroup keyscope="ks_1"> <keydef keys="key_a" href="a.dita" /> <topicgroup keyscope="ks_0"> <keydef keys="key_b" href="b.dita" /> </topicgroup> </topicgroup> </map> - Root scope ks_0.ks_1.key_a=a.dita ks_0.ks_1.ks_0.key_b=b.dita - ks_0 [all from parent] ks_1.key_a=a.dita ks_1.ks_0.key_b=b.dita - ks_1 [all from parent] key_a=a.dita ks_0.key_b=b.dita - ks_0 [all from parent] key_b=b.dita t0.dita is referenced from the root scope, so would use keyref="ks_0.ks_1.key_a" and keyref="ks_0.ks_1.ks_0.key_b", respectively. (VARIATION) If you'd put the keyscope attribute on the <map> element of map-1.ditamap instead of in a nested <topicgroup>, I.e. <!-- map-1.ditamap --> <map keyscope="ks_1"> <keydef keys="key_a" href="a.dita" /> <topicgroup keyscope="ks_0"> <keydef keys="key_b" href="b.dita" /> </topicgroup> </map> Then instead of two nested scopes, you have one scope with two names, e.g. the equivalent of keyscope="ks_0 ks_1". A key in a child scope can be referenced from a parent scope using any of that scope's names as a prefix, so now the scope hierarchy looks like this: - Root scope (ks_1.key_a=a.dita) (ks_1.ks_0.key_b=b.dita) (ks_0.key_a=a.dita) (ks_0.ks_0.key_b=b.dita) - ks_0 ks_1 [all from parent] key_a=a.dita (ks_0.key_b=b.dita) - ks_0 [all from parent] key_b=b.dita And now t0.dita has options. For key_a it can use either keyref="ks_0.key_a" or "ks_1.key_a", and for key_b it can use either "ks_0.ks_0.key_b" or "ks_1.ks_0.key_b". (NOTE: This is the one place where it's worth mentioning cross-publication linking when talking about key scopes. If map-1.dita was referenced via scope="peer" instead of scope="local", then you couldn't reliably use the scope name from the map to reference its keys, because that map might not always be available.) ----------------------------------------------------------- <!-- Sample - 5 - What does the key scope hierarchy look like in map-0.ditamap? - How to reference key_a and key_b explicitly in t_0.dita? (i.e., use dot notation). --> <!-- map-0.ditamap --> <map> <mapref href="map-1.ditamap" keyscope="ks_0"/> <mapref href="map-1.ditamap" keyscope="ks_1"/> <topicref href="t_0.dita"/> </map> <!-- map-1.ditamap --> <map> <topicgroup keyscope="ks_1"> <keydef keys="key_a" href="a.dita" /> <topicgroup keyscope="ks_0"> <keydef keys="key_b" href="b.dita" /> </topicgroup> </topicgroup> </map> - Root scope (ks_0.ks_1.key_a=a.dita) (ks_0.ks_1.ks_0.key_b=b.dita) (ks_1.ks_1.key_a=a.dita) (ks_1.ks_1.ks_0.key_b=b.dita) - ks_0 [all from parent] (ks_1.key_a=a.dita) (ks_1.ks_0.key_b=b.dita) - ks_1 [all from parent] key_a=a.dita (ks_0.key_b=b.dita) - ks_0 [all from parent] key_b=b.dita - ks_1 [identical to root->ks_0] Once again, t0 occurs at the root scope, so it can reference key_a via "ks0.ks_1.key_a" or "ks_1.ks_1.key_a", and key_b via either "ks_0.ks_1.ks_0.key_b" or "ks_1.ks_1.ks_0.key_b". However, note that in this case which one you use implies which copy of the topic should be referenced in the output, and processors should probably honor that, though it's not required by the spec. And again, if the keyscope were on the map instead of on an inner topicgroup, that first level of scoping would collapse, though then ks_1 would always refer to the first child scope, due to precedence rules. ----------------------------------------------------------- (THOUGHTS ON AUTHORING TOOLS) Authoring tools that wish to be keyref-aware needs some way of specifying the context from which key definitions are to be determined. In DITA 1.2, that's as simple as specifying the root map. In DITA 1.3, in addition to a root scope, they additionally need to specify a scope path. For instance, I would expect the user experience when opening a topic from a map will be unchanged, since the system can determine where in the map, and thus the key scope structure, the selected topicref resides, and so the appropriate key space can be determined automatically. For programs that provide a UI allowing the user to specify the key resolution context to use, the process will be more involved for the system, but usually not for the user: 1. User selects root map containing the key context. 2. System determines the key scope path(s) that apply for the given topic in the specified map. When there is exactly one - the most likely scenario in my experience - the system automatically uses that scope, and the user is done. 3. If the specified topic is referenced by the selected map from multiple key scopes, the system needs to prompt the user for which of those scopes to apply. This is also the case if the topic is not referenced from the map, though that might also be considered cause for a warning message to the user, and the system will need to present all possible scope paths. When providing the user with a UI for selecting a key reference to insert, they should be provided from the currently-selected key context, determined as described above. An authoring tool might want to present the list of keys available for a map. Whereas in DITA 1.2, this would be a flat list of effective definitions, it will now need to be hierarchical. It might also want to provide indicators when a given key name is implicitly defined in a child scope vs. explicitly defined within that scope, as I've attempted to do in the above examples using parentheses. There might also be options for showing/hiding implicit qualified keys and overridden keys.