Matching XML document element with XSLT path and xsl:mode on-no-match="fail"
Asked Answered
J

2

2

I have a test XML file:

<?xml version="1.0" encoding="utf-8"?>
<foo>
  <bar>test</bar>
</foo>

I am using this XSLT 3 template:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="text"/>
  <xsl:mode on-no-match="fail"/>

  <xsl:template match="/">
    This is a test.
  </xsl:template>
</xsl:stylesheet>

That outputs as expected, because it found a root (and there should always be a root):

This is a test.

But if I try to match the document element in an absolute way:

…
  <xsl:template match="/foo">
…

That fails; with Saxon 12.2 it says:

XTDE0555 No user-defined template rule in the unnamed mode matches the node /

In fact this doesn't work either:

…
  <xsl:template match="foo">
…

How can I match on the document element of an XML document, preferably using an absolute XPath expression, using XSLT 3?

Update: A ha! If I change the template to this, using the catch-all from https://stackoverflow.com/a/3378562, it matches for /foo:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="text"/>

  <xsl:template match="/foo">
    This is a test.
  </xsl:template>

 <xsl:template match="*">
  <xsl:message terminate="no">
   WARNING: Unmatched element: <xsl:value-of select="name()"/>
  </xsl:message>
  
  <xsl:apply-templates/>
 </xsl:template>

</xsl:stylesheet>

This successfully outputs:

This is a test.

It does not issue any warning. This tells me that /foo must successfully be matching something (which I assume is the document element <foo>).

I wanted a simple switch to say "fail if nothing matches instead of outputting the text of the entire document"; see Why does XSLT output all text by default? . Am I not using <xsl:mode on-no-match="fail"/> correctly in the first example?

Jankey answered 27/5, 2023 at 16:46 Comment(0)
A
1

Your updated transformation has no template matching the document node (/).

And because you have specified: <xsl:mode on-no-match="fail"/>, this is exactly why you are getting the XTDE0555 error message.


To summarize, here is what <xsl:mode on-no-match="fail"/> is designed to do:

As per the XSLT 3.0 Specification:

"The effect of choosing on-no-match="fail" for a mode is that every item selected in an xsl:apply-templates instruction must be matched by an explicit user-written template rule."

. . .

"[ERR XTDE0555] It is a dynamic error if xsl:apply-templates, xsl:apply-imports or xsl:next-match is used to process a node using a mode whose declaration specifies on-no-match="fail" when there is no template rule in the stylesheet whose match pattern matches that node"

Because you have not provided any template matching the document node, when <xsl:apply-templates/> in the default template is executed, it doesn't find any user-specified template matching the document node, and because <xsl:mode on-no-match="fail"/> is specified in the transformation, this triggers raising the error message.


Here is a complete example:

Let us have the simplest possible xml document:

<t/>

And apply on it this transformation:

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  
  <xsl:output method="text"/>
  <xsl:mode on-no-match="fail"/>
  
  <xsl:template match="/">
    This is a test.
    <xsl:apply-templates select="*"/>
  </xsl:template>
  
  <xsl:template match="*">
    Matched element name: <xsl:value-of select="name()"/>
  </xsl:template>
</xsl:stylesheet>

We get the expected result:

This is a test.

Matched element name: t

enter image description here

Now, remove, or comment out the template matching the document node:

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  
  <xsl:output method="text"/>
  <xsl:mode on-no-match="fail"/>
  
<!--  <xsl:template match="/">
    This is a test.
    <xsl:apply-templates select="*"/>
  </xsl:template>
-->  
  <xsl:template match="*">
    Matched element name: <xsl:value-of select="name()"/>
  </xsl:template>
</xsl:stylesheet>

Run the updated transformation:

The result is:

Severity: fatal
Problem ID: XTDE0555
Description: No user-defined template rule in the unnamed mode matches the node /
URL: http://www.w3.org/TR/xslt20/#err-XTDE0555

enter image description here

Exactly what we should get having specified: <xsl:mode on-no-match="fail"/> An xsl:apply-templates instruction selected a node (the document-node() or \) but there is no user-provided template that matches this node.

One may wonder "Who issued this xsl:apply-templates instruction?

The answer is: this is in the built-in (default) template matching / (again one needs to learn more about the XSLT processing model and the default templates).

It is exactly defined in the XSLT 3.0 Specification as:

<xsl:template match="document-node()|element()" mode="M">
  <xsl:apply-templates mode="#current"/>
</xsl:template>
Adrianaadriane answered 27/5, 2023 at 19:25 Comment(8)
I think there's something I'm misinterpreting. You say "Your updated transformation has no template matching the document node (/)." You mean to tell me that <xsl:template match="/foo"> does not match the document node? Then why does it work and output This is a test. when I remove the <xsl:mode on-no-match="fail"/>?Jankey
Maybe you're saying that in both cases the "root" is not being matched, but that <xsl:template match="*"> somehow doesn't detect this but <xsl:mode on-no-match="fail"/> does detect that? So why wouldn't <xsl:template match="*"> detect that /foo doesn't match the root? I confess I'm still confused.Jankey
"… when there is no template rule in the stylesheet whose match pattern matches that node". Yet when I change the template to only include <xsl:template match="*">, then <xsl:mode on-no-match="fail"/> still fails and says that / isn't matched. But doesn't <xsl:template match="*"> match the root? I'm getting more confused by the minute.Jankey
P.S. Are there any full examples on the web of using <xsl:mode on-no-match="fail"/> that don't require me to create an account to view?Jankey
@GarretWilson. /foo matches the top element in the document (sometimes called document element) but no, it does not match the document node (/). Just try adding a template that matches document-node() or just / and contains simply an <xsl:apply-templates/> instruction, and you will see that the transformation will be performed with no error. I will try to provide a full example, but in the future, please at least do your homework.Adrianaadriane
@GarretWilson I udated the answer with a complete example, as requested. If you understand the XSLT processing model and its built-in templates, you should not have problems understanding this example.Adrianaadriane
@GarretWilson Re: "But doesn't <xsl:template match="*"> match the root? I'm getting more confused by the minute. " Of course not. The template you are asking about matches any element. I does not match the root, aka document-node() aka the document node.Adrianaadriane
@GarretWilson AAnd here is again the reference to the Pluralsight courses (deleted by a moderator with suggestion to add these as a comment): You can learn more about <xsl:mode on-no-match="fail"/> in this Pluralsight course on XSLT 3.0: "What's New in XSLT 3.0: Part 1" (app.pluralsight.com/library/courses/xpath-3-0-whats-new/…) And more precisely in this module: "Error Handling in XSLT 3.0" (app.pluralsight.com/…) , and in this clip of the module: "The Fail built-In Template." ()Adrianaadriane
M
1

Processing starts at the root node of the document (the document node, not to be confused with the "document element" - a term that's not used in XSLT). The XSLT processor is therefore looking for a template rule with match="/". You don't have such a rule. Normally this would cause the default rule to be invoked, which does apply-templates select="child::node()", which would cause your match="/foo" rule to be invoked. But you specified on-no-match="fail", and that means that when there is no match="/" rule you get an error rather than the fallback processing.

Murk answered 27/5, 2023 at 20:16 Comment(5)
(Just a note that I was very involved with XML throughout its heyday, even writing an XML parser, but I never got into XSLT until now—although I did implement a bit of XPath 1.0 from scratch.) Oh, I think It's beginning to dawn on me. I thought the template could "reach out" and "pull" whatever sections it wants to from the document (i.e. random access) and ignore the rest. So you're saying XSLT is more of a "push" model in which the template must have something matching everything in the XML document that is "pushed" to it, starting from the root. Is that a more correct understanding?Jankey
But wait, I'm still not getting it. If I remove <xsl:mode on-no-match="fail"/>, then <xsl:template match="/foo"> will successfully output "This is a test." I didn't change anything in the match part.Jankey
Oh, if I remove <xsl:mode on-no-match="fail"/>, then it outputs "This is a test" because of that unusual rule where it matches something even if nothing matches. I think I'm starting to untangle some things. I still don't understand why <xsl:mode on-no-match="fail"/> fails even if I have xsl:template match="*">, though, because intuitively I would think xsl:template match="*"> matches everything, even the root.Jankey
No, xsl:template match="*" doesn't match everything. It only matches element nodes. It doesn't match a document node, which is where the processing starts.Murk
A bit of terminology. The data model XSLT uses is called the XDM model. In the XDM model, the node at the root of the tree is called a document node. The outermost element doesn't have a special name (but I call it the outermost element). XML calls this "the root, or document element" (some people read this as "the root element or document element"), but in XDM the root of the tree is a document node, and the outermost element node is one of its children (other children may include comments and processing instructions).Murk

© 2022 - 2024 — McMap. All rights reserved.