Edit online

Resolving DITA Cross-References in Refactoring Operations

28 Jul 2023
Read time: 7 minute(s)

We needed a way to resolve DITA <xref> and <link> elements to their target element in a refactoring operation. We also needed to determine whether the link was a local or peer-map reference. Fortunately, Oxygen v25.1 provided us with the solution!

The Missing Piece - Resolving a @keyref

For <xref> and <link> elements, we use a mix of @href and @keyref references in our content. In XSLT refactoring operations, we could resolve @href references to the target file (and optionally, an element in that file) by using the XPath document() function. However, we had no way of resolving @keyref references because a refactoring operation only processes the current file (it doesn't consider Oxygen's map context or the keys defined within it). In addition, writing such a resolver in XSLT that properly considers keyscopes would be enormously difficult.

In the Oxygen v25.1 release, new API functions were introduced to provide information about @keyref references:

  • The getKeyRefInfo() function returns information about a reference's type (local, peer, unresolved, and so on).

  • The getKeyRefAbsoluteReference() function resolves a @keyref to its equivalent @href reference URL.

Note:

There were subsequent improvements to the API functions in Oxygen v25.1 build 2023070306, so you should use that release or later for best results.

Thanks to this new API, we could use a simple @href resolver written in XSLT for @keyref references too!

Resolving Cross-References in Refactoring Operations

The attached test case provides the following XSLT file that you can include in your own refactoring operations:

frameworks/dita/refactoring/util-get-referenced-element.xsl

This file defines a mode="get-referenced-element" template that, when applied to any element with an @href or @keyref attribute, returns the referenced element. If the reference cannot be resolved, the template returns an empty sequence.

The template works as follows:

  1. References with @scope="external" always return an empty sequence.

  2. References with @format set to a value other than "dita" always return an empty sequence.

  3. If the reference has a @keyref, it is converted to an @href value using getKeyRefAbsoluteReference().

  4. The @href value is parsed into its components as follows:

    [file]#topic_id[/element_id]

  5. The target document is obtained as follows:

    • If no file is specified, the in-memory document that contains the cross-reference element is used.

    • If a file is specified and that file contains the cross-reference element, the in-memory document that contains the cross-reference element is used.

    • Otherwise, the specified file document is loaded from disk using the XPath document() function.

    This heuristic approach ensures that in multiple-pass refactoring operations, the in-memory version of the content is preferred over the on-disk version.

  6. The topic that matches the topic_id value is obtained from the target document.

  7. If an element_id is specified, the element in the topic that matches the element_id value is obtained.

    Because non-topic @id values do not need to be unique, the code ensures that no subtopics within the matching topic are searched to avoid incorrect matches.

To view the XSLT stylesheet without downloading the archive, click on the following link:

util-get-referenced-element.xsl

There are comments in the code to explain how it works.

The @keyref API functions require that a map context be active in Oxygen. If no context is available (for example, no map is open in the DITA Maps Manager), there will be no key information available to resolve the reference.

Example Test Case

The following Oxygen project provides examples of how cross-reference resolutions can be used in refactoring operations and Schematron checks:

resolving_refs_refactoring.zip

Specifically, it provides the following:

  • An "Update Cross References" refactoring operation is provided that:

    • Sets (or updates) the @type attribute for <xref> and <link> elements.



    • Populates the target text for <xref> and <link> elements that reference topics in peer maps (i.e. cross-book links).



  • Schematron checks are provided that:

    • Warn about <xref> and <link> elements that reference topics in peer maps (i.e. cross-book links) but do not contain any target text.



      This check also offers a "quick fix" that populates the target text for you.

    • Show the value of the getKeyRefInfo() and getKeyRefAbsoluteReference() API calls for any element with a @keyref attribute.



      These informational checks are commented out by default. You can uncomment them in the following file:

      frameworks/dita/sch/checks.sch

When target text is added to a peer map (cross-book) reference, an <?oxy-peertext?> processing instruction is added to indicate that the text was updated automatically. If you remove this processing instruction and customize the target text, your customized text will not be disturbed by future automatic updates.