XSLT - Exclude Node If It Contains Invalid Data - xslt

I'm trying to check the value of a child node and include it in the transformed XML only if it it's value isn't -1. Below is a sample of the input xml.
<root>
<a>-1</a>
<b>valid</b>
<c>valid</c>
<d>valid</d>
</root>
And the relevant section of the XSLT:
<xsl:choose>
<xsl:when test="/a != -1">
<xsl:value-of select="*"></xsl:value-of>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="*[not(a)]"></xsl:value-of>
</xsl:otherwise>
</xsl:choose>
Elements b through c are permitted to have values of -1, so I need to check the <a> element for that value specifically, and only it should be excluded. Currently I do the -1 check in a choose block, and try to exclude it by using <xsl:value-of select="*[not(a)]"></xsl:value-of>. Any help is appreciated.

If you use the standard recursive descent processing model using xsl:apply-templates, then you just need a do-nothing rule
<xsl:template match="a[.=-1]"/>
to exclude these elements.
If you're using some other processing model then you need to show us your code.

Related

xslt in xml attribute

I have xml element tyle boleean.
<testelement>0</testelement>
I use xslt to transform value to no/yes depending on 0/1 value and it works great
<xsl:choose>
<xsl:when test="./text()='0'">
<xsl:text>No</xsl:text>
</xsl:when>
<xsl:when test="./text()='1'">
<xsl:text>Yes</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="yes">The Yes/No value to be translated did not match expected input</xsl:message>
</xsl:otherwise>
</xsl:choose>
The same I try to do with attribute type boolean. Element has maxOcc unbounded.
<element attribute="0">
...
</element>
<element attribute="1">
...
</element>
In xlts:
<xsl:choose>
<xsl:when test="//#attribute='0'">
<xsl:text>No</xsl:text>
</xsl:when>
<xsl:when test="//#attribute='1'">
<xsl:text>Yes</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="no">The Yes/No value to be translated did not match expected input</xsl:message>
</xsl:otherwise>
</xsl:choose>
But after i use this code all values are Yes or all values are No depending what is value in first node element. EG if 0 is in first element all values are No and it doesnd matter that in second is 1.
How to transform it properly?
Thanks
all values are Yes or all values are No
depending what is value in first node element
Yes, of course they are. That's because your test:
<xsl:when test="//#attribute='0'">
selects all the attributes in the XML document, and in XSLT 1.0 (which I assume you're using) only the first one's value will be used.
You need first to be in the context of element, then test that specific element's attribute by:
<xsl:when test="#attribute='0'">
Here's a better way to do it:
<xsl:template match="node()[.='0'] | #*[.='0']" mode="toYesNo"/>No</xsl:template>
<xsl:template match="node()[.='1'] | #*[.='1']" mode="toYesNo"/>Yes</xsl:template>
<xsl:template match="node()|#*" mode="toYesNo"/>
<xsl:message terminate="no">The Yes/No value to be translated did not match expected input</xsl:message>
</xsl:template>
and then you can xsl:apply-templates (with mode="toYesNo") selecting any element, attribute, or text node to get the appropriate conversion.
In XSLT 3.0 you can replace the patterns withm for example, match=".[.='0']" to match any kind of node.
Try to avoid using ./text() because it goes wrong when there are comments in your XML. You can nearly always replace it with ..
And of course you error with the attributes was the leading //. You need to be very clear about the difference between absolute path expressions (which start with / and select from the root of the tree) and relative path expressions (which select from the node you are current processing).

XPath query to get a sub-node depending whether the node name starts with a number or not

I have the following XML structure:
<products>
<C8TJM>
<code>C8TJM</code>
<name>Product</name>
<description>Product description</description>
</C8TJM>
<D75KF>
<code>D75KF</code>
<name>Product</name>
<description>Product description</description>
</D75KF>
<_89TJX>
<code>89TJX</code>
<name>Product</name>
<description>Product description</description>
</_89TJX>
</products>
Each sub-node represents a product and the name of the node is the product code. Because XML doesn't allow nodes to start with a number an underscore is automatically added to the name of the node.
I know the product code upfront.
I have to write an XPath query to:
first check if the product code starts with a number, if it does,
then prefix it with an underscore;
search through the tree for that
product code and get the description.
Later edit
Managed to write this variable definition:
<xsl:variable name="prefixedProductCode">
<xsl:choose>
<xsl:when test="contains('0123456789', substring($productCode, 1, 1))">
<xsl:value-of select="concat('_', $productCode)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$productCode" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
But I wanted to do it on the fly, in the XPath query, without having to define a new variable.
Final implementation:
//products/node()[name() = $productCode]/description

check if repeating node is empty in xslt

I have xml like defined below . The node EducationDetails can repeat (unbounded).
<PersonalDetailsResponse>
<FirstName></FirstName>
<LastName></LastName>
<EducationDetails>
<Degree></Degree>
<Institution></Institution>
<Year></Year>
</EducationDetails>
<EducationDetails>
<Degree></Degree>
<Institution></Institution>
<Year></Year>
</EducationDetails>
</PersonalDetailsResponse>
I want to create another xml from the above one using xslt.
My requirement is, if there is no data in any of the EducationDetails child nodes , then the resulting xml has to get data from another source.
My problem is , I am not able to check if all the EducationDetails child nodes are empty.
Since variable value cannot be changed in xslt , I tried using saxon with below code.
xmlns:saxon="http://saxon.sf.net/" extension-element-prefixes="saxon"
<xsl:variable name="emptyNode" saxon:assignable="yes" select="0" />
<xsl:when test="count(ss:education) > 0">
<xsl:for-each select="ss:education">
<xsl:if test="not(*[.=''])">
<saxon:assign name="emptyNode">
<xsl:value-of select="1" />
</saxon:assign>
</xsl:if>
</xsl:for-each>
<xsl:if test="$emptyNode = 0">
<!-- Do logic if all educationdetails node is empty-->
</xsl:if>
</xsl:when>
But it throwing exception "net.sf.saxon.trans.XPathException: Unknown extension instruction " .
It looks like saxon 9 jar is required for it ,which I am not able to get from my repository.
Is there a simpler way to check if all the child nodes of are empty.
By empty I mean, child nodes might be present, but no value in them.
Well, if you use <xsl:template match="PersonalDetailsResponse[EducationDetails[*[normalize-space()]]">...</xsl:template> then you match only on PersonalDetailsResponse element having at least one EducationDetails element having at least one child element with non whitespace data. As you seem to use an XSLT 2.0 processor you can also use the perhaps clearer <xsl:template match="PersonalDetailsResponse[some $ed in EducationDetails/* satisfies normalize-space($ed)]">...</xsl:template>.
Or perhaps if you want a variable inside the template use
<xsl:template match="PersonalDetailsResponse">
<xsl:variable name="empty-details" select="not(EducationDetails/*[normalize-space()])"/>
<xsl:if test="$empty-details">...</xsl:if>
</xsl:template>
With XSLT 2.0 the use of some or every satisfies might be easier to understand e.g.
<xsl:template match="PersonalDetailsResponse">
<xsl:variable name="empty-details" select="every $dt in EducationDetails/* satisfies not(normalize-space($dt))"/>
<xsl:if test="$empty-details">...</xsl:if>
</xsl:template>
But usually writing templates with appropriate match conditions saves you from using xsl:if or xsl:choose inside of a template.

XSL:if test or XSL:choose

XML:
<?xml version="1.0" encoding="utf-8"?>
<NewDataSet>
<inc_incident>
<inc_interventionprocedure>
<ProcedureID>CPR</ProcedureID>
</inc_interventionprocedure>
<inc_interventionprocedure>
<ProcedureID>Airway-Endotracheal Intubation</ProcedureID>
</inc_interventionprocedure>
<inc_interventionprocedure>
<ProcedureID>Capnography</ProcedureID>
</inc_interventionprocedure>
</inc_incident>
</NewDataSet>
XSL:
<xsl:if test="starts-with(inc_interventionprocedure/ProcedureID, "Airway")">
<fo:inline>X</fo:inline>
</xsl:if>
<xsl:if test="not(starts-with(inc_interventionprocedure/ProcedureID, "Airway"))">
<fo:inline>X</fo:inline>
</xsl:if>
I would like to show, if any of the nodes starts with "Airway", in the column "YES" with an "X" and if there is none in the column "NO" mark "X". With this xsl:if test both column is marked with Xs.
Result with the xsl:if test shows:
YES NO
Airway Established X X
The solution provided by Tim C should work with a minor change as below.
<xsl:choose>
<xsl:when test="inc_interventionprocedure/ProcedureID[starts-with(., 'Airway')]">
<fo:inline>X</fo:inline>
<fo:inline> </fo:inline>
</xsl:when>
<xsl:otherwise>
<fo:inline> </fo:inline>
<fo:inline>X</fo:inline>
</xsl:otherwise>
</xsl:choose>
Your current test is not well-formed as you have quotation marks embedded inside quotation marks. But the main issue is that starts-with takes a string as a first parameter, not a node-set. In XSLT 1.0, it would use the value of the first node. In XSLT 2.0, you would get an error.
Your expression should really look like this
<xsl:if test="inc_interventionprocedure/ProcedureID[starts-with(., 'Airway')]">
Ideally, you would use an xsl:choose here to avoid writing the whole expression again inside a not
Try this:
<xsl:choose>
<xsl:when test="inc_interventionprocedure/ProcedureID[starts-with(., 'Airway')]">
<fo:inline>YES</fo:inline>
</xsl:when>
<xsl:otherwise>
<fo:inline>NO</fo:inline>
</xsl:otherwise>
</xsl:choose>
This assumes you are positioned on a inc_incident element.

XSLT:How to deal with testing the value of an element?

I have an xml file in which there is tag namely, <Gender/> It carries either 'M' or 'F' as data, now my work is to test the value and write <Gender_Tag>Male</Gender_Tag> or <Gender_Tag>Female</Gender_Tag> according to the values M or F respectively .. I tried this code .. It used to work in other circumstances..
All relative paths expressed in a template are evaluated against the current node. Your template match Gender elements, so Gender='M' returns true if there is any Gender's child named 'Gender' with the value 'M'. I guess this is not the case...
Use the dot to express the current node (here a Gender element):
<xsl:template match="root/details/Gender">
<Gender_Tag>
<xsl:choose>
<xsl:when test=".='M'">
<xsl:text>Male</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>Female</xsl:text>
</xsl:otherwise>
</xsl:choose>
</Gender_Tag>
</xsl:template>
EDIT: You may use two templates too
<xsl:template match="root/details/Gender[.='M']">
<Gender_Tag>Male</Gender_Tag>
</xsl:template>
<xsl:template match="root/details/Gender[.='F']">
<Gender_Tag>Female</Gender_Tag>
</xsl:template>
<xsl:template match="root/details/Gender">
<xsl:choose>
<xsl:when test="normalize-space(text())='M'">
<Gender_Tag>Male</Gender_Tag>
</xsl:when>
<xsl:otherwise>
<Gender_Tag>Female</Gender_Tag>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
My example differs in two points from Scoregraphic's:
It uses xsl:choose to ensure, that only one Gender_Tag element is created (that also means, that if the text is not 'M', it is always a Female)
Use of normalize-space() strips white space around the text content of the element.
Untested, but may work...
<xsl:template match="root/details/Gender">
<xsl:if test="text()='M'">
<Gender_Tag>Male</Gender_Tag>
</xsl:if>
<xsl:if test="text()='F'">
<Gender_Tag>Female</Gender_Tag>
</xsl:if>
</xsl:template>
Without seeing XML its hard to be certain, but I think your sample XSLT should be:
<xsl:template match="root/details/Gender">
<xsl:if test=".='M'">
<Gender_Tag><xsl:text>Male</xsl:text></Gender_Tag>
</xsl:if>
<xsl:if test=".='F'">
<Gender_Tag><xsl:text>Female</xsl:text></Gender_Tag>
</xsl:if>
</xsl:template>
Use of choose as per another answer would be better (though I think it should be two explicit when clauses rather than a when and an otherwise)