Project

Profile

Help

Support #6521

closed

Node not matching when not in "pretty print"

Added by Justin Roegner 4 months ago. Updated 4 months ago.

Status:
Closed
Priority:
Low
Assignee:
-
Category:
-
Sprint/Milestone:
-
Start date:
2024-08-29
Due date:
% Done:

0%

Estimated time:
Legacy ID:
Applies to branch:
Fix Committed on Branch:
Fixed in Maintenance Release:
Platforms:

Description

Match always found for ancestor::*[matches(@visibleWhen, '.*!=\s?null')][1] = current() when the source document matching elements are not separated by a text node.

This is the stripped down source xml:

<?xml version="1.0" encoding="UTF-8"?>
<cages><FileRoot name="Page1.xml"><content visibleWhen="level1 != null"><content visibleWhen="level2 != null"><content visibleWhen="level3 != null"><property name="standalone_prop"/></content></content></content></FileRoot></cages>

This is the xslt:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:output method="html" indent="yes"/>
	<xsl:template match="/">
		<html>
			<head>
				<style>
				
					table {
					  width: 100%;
					  border: 1px solid #ddd;
					}
					
					.subtable tr:nth-child(even) {
					  background-color: #eee;
					}
					
					.headerbg {
					  background-color: #ffff66;
					}
					
					.subtablehighlight tr:hover {background-color: #ffffe6;}
					
					.column1 {
						width: 80px;
					}
					
					fieldset {
						margin-bottom: 30px;
					}
					
				</style>
			</head>
			<body>
				<h1>Visible When</h1>
				<!-- Select all files -->
				<xsl:apply-templates select="//FileRoot"/>
			</body>
		</html>
	</xsl:template>
   
	<xsl:template match="FileRoot">
		<!-- Main section per file -->
		<fieldset>
			<!-- File name -->
			<legend><xsl:value-of select="./@name"/></legend>
			<!-- Only grab parent visibleWhen elements; children get addressed within the parent -->
			<xsl:apply-templates select="descendant::*[matches(@visibleWhen, '.*!=\s?null.*') and not(contains(string-join(ancestor::*/local-name(), ' '), ./local-name()))]"/>
		</fieldset>
	</xsl:template>
	
	<xsl:template match="*[matches(@visibleWhen, '.*!=\s?null')]">
		<!-- sub table of each content/page and its subsequent properties -->
		<table class="subtable subtablehighlight" style="margin-bottom:10px" border="3">
			<tr>
				<!-- Header with element name and the visible when clause -->
				<th class="column1 headerbg"><xsl:value-of select="local-name()"/></th>
				<th style="text-align:left" class="headerbg"><xsl:value-of select="./@visibleWhen"/></th>
			</tr>
			<!-- Show properties, injections and labels only if they are under the current visibleWhen -->
			<!-- <xsl:apply-templates select=".//property[generate-id(ancestor::*[matches(@visibleWhen, '.*!=\s?null')][1]) = generate-id(current())]"/> -->
			<xsl:apply-templates select=".//property[ancestor::*[matches(@visibleWhen, '.*!=\s?null')][1] = current()]"/>
			<!-- Pass current element to sub-template -->
			<xsl:call-template name="ChildVisibleWhen">
				<xsl:with-param name="parentNode" select="."/>
			</xsl:call-template>
		</table>

	</xsl:template>
	
	<xsl:template match="property">
		<tr>
			<td class="column1">Property:</td>
			<td><xsl:value-of select="./@name"/></td>
		</tr>
	</xsl:template>

	<xsl:template name="ChildVisibleWhen">
		<xsl:param name="parentNode"/>
		<tr>
			<td/>
			<!-- <td><xsl:apply-templates select="descendant::*[matches(@visibleWhen, '.*!=\s?null.*')][generate-id(ancestor::*[matches(@visibleWhen, '.*!=\s?null')][1]) = generate-id($parentNode)]"/></td> -->
			<td><xsl:apply-templates select="descendant::*[matches(@visibleWhen, '.*!=\s?null.*')][ancestor::*[matches(@visibleWhen, '.*!=\s?null')][1] = $parentNode]"/></td>
		</tr>
	</xsl:template>
	
</xsl:stylesheet>

I expect this:

I get this:

I can add a space or newline between any <content> and then it works fine. Since I can't guarantee the source will be formatted with a text node I've worked around it by using the generate-id function. From what I can tell I'm comparing single node sets so they should match regardless. I'm not seeing what is unique that comparing in these different scenarios should change the result.

line 62/63 and 83/84 are in here to make toggling between a working and not working template easier.

I've validated this happens in the saxon-he-12.5 w/xmlresolver-5.2.2.jar


Files

Actions #1

Updated by Martin Honnen 4 months ago

Perhaps you are looking for the is operator that checks node identity:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<xsl:output method="html" indent="yes"/>
	<xsl:template match="/">
		<html>
			<head>
				<style>
				
					table {
					  width: 100%;
					  border: 1px solid #ddd;
					}
					
					.subtable tr:nth-child(even) {
					  background-color: #eee;
					}
					
					.headerbg {
					  background-color: #ffff66;
					}
					
					.subtablehighlight tr:hover {background-color: #ffffe6;}
					
					.column1 {
						width: 80px;
					}
					
					fieldset {
						margin-bottom: 30px;
					}
					
				</style>
			</head>
			<body>
				<h1>Visible When</h1>
				<!-- Select all files -->
				<xsl:apply-templates select="//FileRoot"/>
			</body>
		</html>
	</xsl:template>
   
	<xsl:template match="FileRoot">
		<!-- Main section per file -->
		<fieldset>
			<!-- File name -->
			<legend><xsl:value-of select="./@name"/></legend>
			<!-- Only grab parent visibleWhen elements; children get addressed within the parent -->
			<xsl:apply-templates select="descendant::*[matches(@visibleWhen, '.*!=\s?null.*') and not(contains(string-join(ancestor::*/local-name(), ' '), ./local-name()))]"/>
		</fieldset>
	</xsl:template>
	
	<xsl:template match="*[matches(@visibleWhen, '.*!=\s?null')]">
		<!-- sub table of each content/page and its subsequent properties -->
		<table class="subtable subtablehighlight" style="margin-bottom:10px" border="3">
			<tr>
				<!-- Header with element name and the visible when clause -->
				<th class="column1 headerbg"><xsl:value-of select="local-name()"/></th>
				<th style="text-align:left" class="headerbg"><xsl:value-of select="./@visibleWhen"/></th>
			</tr>
			<!-- Show properties, injections and labels only if they are under the current visibleWhen -->
			<!-- <xsl:apply-templates select=".//property[generate-id(ancestor::*[matches(@visibleWhen, '.*!=\s?null')][1]) = generate-id(current())]"/> -->
			<xsl:apply-templates select=".//property[ancestor::*[matches(@visibleWhen, '.*!=\s?null')][1] is current()]"/>
			<!-- Pass current element to sub-template -->
			<xsl:call-template name="ChildVisibleWhen">
				<xsl:with-param name="parentNode" select="."/>
			</xsl:call-template>
		</table>

	</xsl:template>
	
	<xsl:template match="property">
		<tr>
			<td class="column1">Property:</td>
			<td><xsl:value-of select="./@name"/></td>
		</tr>
	</xsl:template>

	<xsl:template name="ChildVisibleWhen">
		<xsl:param name="parentNode"/>
		<tr>
			<td/>
			<!-- <td><xsl:apply-templates select="descendant::*[matches(@visibleWhen, '.*!=\s?null.*')][generate-id(ancestor::*[matches(@visibleWhen, '.*!=\s?null')][1]) = generate-id($parentNode)]"/></td> -->
			<td><xsl:apply-templates select="descendant::*[matches(@visibleWhen, '.*!=\s?null.*')][ancestor::*[matches(@visibleWhen, '.*!=\s?null')][1] is $parentNode]"/></td>
		</tr>
	</xsl:template>
	
</xsl:stylesheet>
Actions #2

Updated by Michael Kay 4 months ago

The expression

ancestor::*[matches(@visibleWhen, '.*!=\s?null')][1] = current()

is comparing the string value of the first ancestor node that matches the pattern to the string value of the current node. The string value of a node is the concatenation of its descendant text nodes, so whitespace text nodes are going to affect the outcome.

I haven't followed through your logic, but the fact that it works with a generate-id() comparison does suggest that you used the "=" operator when you intended "is".

I'm wondering if there is a better way of writing

.//property[ancestor::*[matches(@visibleWhen, '.*!=\s?null')][1] is current()]

Perhaps something like

.//property except .//*[matches(@visibleWhen, '.*!=\s?null')]//property

though that's not obviously much of an improvement.

Actions #3

Updated by Justin Roegner 4 months ago

Thanks for the explanation. It makes sense now why it was bringing in the text node when doing that comparison. Both the "is" and "except" methods are logical resolutions to resolve what I thought was a bug. It was just an error on my part. Thanks again. I really appreciate it.

Actions #4

Updated by Michael Kay 4 months ago

  • Tracker changed from Bug to Support
  • Status changed from New to Closed

Please register to edit this issue

Also available in: Atom PDF