5

Is it possible to match 'Any node not already matched/handled'? Preferably without making my style sheet into an enormous for-each/Choose statement, as the current ordering is critical.

The project is still in development and already being used in the live environment, so the code is, simply put, in a state of rapid flux in response to live data we're getting in. I am generating a PDF document via FO from XML that may have nodes in it that I don't know about yet, and would like to add a "fail over" instruction to my XSL-FO transform that puts all the unaccounted-for nodes at the beginning of the document in bright red, to speed up discovery.

I can't just ignore surprise nodes, as the data needs to be handled. The quicker I can find "orphan" data, the quicker I can get it properly handled and out the door.

I have tried playing around with <xsl:template match="*">...</xsl:template> and various priority="" settings, but of course it applies to every node.

For example, I might have this in one section because these XML blocks are not guarenteed to come in the correct output order. (Code block formatting is not working for me - four space indent results in nothing, sorry :(

<xsl:template match="AccountSummary">
    <fo:block margin-left="2" space-before="1" space-after="1" text-align="center">
        <xsl:apply-templates select="Label"/>
    </fo:block>
    <xsl:apply-templates select="AccountInfo"/>
    <xsl:apply-templates select="AccountProfile"/>
    <xsl:apply-templates select="ChangeInValueOfAccounts"/>
    <!-- ... more goes here -->
</xsl:template>

I would like to do something like

<xsl:template match="AccountSummary">
    <fo:block margin-left="2" space-before="1" space-after="1" text-align="center">
        <xsl:apply-templates select="Label"/>
    </fo:block>
    <xsl:apply-templates select="AccountInfo"/>
    <xsl:apply-templates select="AccountProfile"/>
    <xsl:apply-templates select="ChangeInValueOfAccounts"/>
    <!-- ... more goes here -->
    <xsl:for-each select="not otherwise matched">
        <!-- call zomgRED template -->
    </xsl:for-each>
</xsl:template>

Ideally I'd rather the zomgREDs to be at the top, but at the bottom will work too. Or flagged with text markers. Anything to spit out the text in the final document instead of silently eating it.

Pavel Veller
  • 6,085
  • 1
  • 26
  • 24
BrunoXSLT
  • 53
  • 1
  • 5
  • 2
    Since XSLT prioritizes matches with an explicit hierarchy based on template specificity, among other things, you should be able to use `match='*'` for this. It might help if you posted a small sample XML and XSLT. The requirement to put all unknown nodes _at the top_ is going to be hard, as you don't know which nodes are unmatched until the entire document has been processed. – Jim Garrison May 04 '12 at 18:32
  • Ah, I of course forgot to escape my <s. I can cope with "in order" or wherever if necessary, but at the top would be simpler. – BrunoXSLT May 04 '12 at 18:47
  • Added escapes to original post. In brief, when I try <xsl:template match="*"><fo:block>##### <xsl:apply-templates />&lt/xsl:template> it applies instead of any other template being applied by match= instead of by select= and suddenly each node gets spat out in its own block with ##### in front of it :/ – BrunoXSLT May 04 '12 at 18:50
  • shot in the dark... have you tried ``? – mindandmedia May 04 '12 at 18:53
  • the `*` or `node()` isn't guaranteed to capture all those unprocessed nodes. there's got to be a recursive call from each specific template to make sure all nodes are touched by at least `some` template. see what I mean? if I have some `match="myNode"` somewhere that doesn't call `` within then no "new" child nodes of that `myNode` will get caught. I hope my explanation makes sense – Pavel Veller May 04 '12 at 18:55
  • I foresee time spent this weekend touching every template... _sigh_ I probably should have seen this coming in the first place though. I'll know better for the next crisis anyways. – BrunoXSLT May 04 '12 at 19:00

3 Answers3

2

Here's how you might be able to get some flavor of what you are looking for. Though no pushing the unprocessed nodes up to the top:

1) have the identity transformation in your stylesheet:

<xsl:template match="@* | node()">
    <xsl:copy>
        <xsl:apply-templates select="@* | node()"/>
    </xsl:copy>
</xsl:template>

2) make sure you call <xsl:apply-templates select="*"/> from all other more specific templates to let the parser going

The idea is that your specific templates handle the intended transformation to XSL-FO while the identity transform just dumps out everything else into the result tree. The recursive calls will ensure you actually touch all nodes (this is the only way to get them "on record") and the identity transformation will match those you didn't catch otherwise.

That #2 advise can actually get in a way of your XSL-FO logic so may not really be feasible, I can't tell without having an example input and your current XSL-FO transform. Maybe add it to the question?

Pavel Veller
  • 6,085
  • 1
  • 26
  • 24
0

note: original answer completely replaced based on updated question.

You can do so, but you will need to list those items you don't want to get the zomgRed template a second time:

<xsl:template match="AccountSummary"> 
    <fo:block margin-left="2" space-before="1" space-after="1" text-align="center"> 
        <xsl:apply-templates select="Label"/> 
  </fo:block> 
  <xsl:apply-templates select="AccountInfo"/> 
  <xsl:apply-templates select="AccountProfile"/> 
  <xsl:apply-templates select="ChangeInValueOfAccounts"/> 
  <-- ... more goes here --> 
    <xsl:apply-templates mode='fallback' select * />

</xsl:template>

<xsl:template match='AccountInfo|AccountProfile|ChangeInValueOfAccounts| ... more goes here' mode='fallback' />

<xsl:template match='*' mode='fallback'>
   <!-- call zomgRED template --> 
</xsl:template>

I've replaced your for-each with another apply-templates, but now we are using a mode. Only templates in the same mode will match. The template that matches "*" only matches in that mode, so it won't get called in other instances. The one-line template matches in the same mode, and matches all of the nodes you have already handled, and does nothing. Since it is more specific than "*", it will run (and do nothing) instead of the "*" template for those nodes.

As an alternative to the single line template with it's duplicate list of nodes, you can add the same mode to all of the existing templates (AccountInfo, AccountProfile, etc) and to the apply-templates statement for each. It's a matter of style, and which you find more maintainable.

Jason Clark
  • 1,433
  • 1
  • 16
  • 24
0

Here is an example how this can be done (only tracking unmatched nodes -- if necessary unmatched attributes tracking can be implemented in the same way):

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <!-- Catch all node - types template -- must be the top-most -->
 <xsl:template match="node()">
  Warning: this node is unmatched by any templates:

  "<xsl:copy-of select="."/>"
 </xsl:template>

 <xsl:template match="nums">
  <xsl:apply-templates/>
 </xsl:template>

 <xsl:template match="num[following-sibling::num[1] -1 = .]">
  <xsl:copy-of select="."/>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the following XML document: (intentionally chosen very simple, in order to illustrate this simple solution)

<nums>
  <num>01</num>
  <num>02</num>
  <num>03</num>
  <num>04</num>
  <num>05</num>
  <num>06</num>
  <num>07</num>
  <num>08</num>
  <num>09</num>
  <num>10</num>
</nums>

the result is produced, including a warning for one unmatched element:

<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
  Warning: this node is unmatched by any templates:

  "<num>10</num>"

Do note: This solution will not work if the stylesheet imports/includes other stylesheets. In such case the catch-all template(s) must be placed in the stylesheet that comes first (or has lowest import precedence).

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431