Check existence of child node and applying templates - xslt

I have the following simplified XML structure:
<?xml version="1.0" encoding="UTF-8"?>
<ExportData>
<TransportHeader>
<Timestamp>2011-01-16 06:00:33</Timestamp>
<From>
<Name>DynamicExport</Name>
<Version>1.</Version>
</From>
<MessageId>d7b5c5b69a83</MessageId>
</TransportHeader>
<ExportConfig>
<DateTimeFormat>yyyy-MM-dd HH:mm:ss</DateTimeFormat>
<DecimalSymbol>.</DecimalSymbol>
</ExportConfig>
<DataSet>
<Tables>
<Table>
<RH>...</RH>
<Rows>
<R>Data1</R>
<R>Data2</R>
<R>Data3</R>
<R>Data4</R>
<R>Data5</R>
</Rows>
</Table>
</Tables>
</DataSet>
</ExportData>
I have to check if <R> elements exist or not. If no <R> elements exist the mapping has to be aborted, otherwise a <Line> element per <R> needs to be created.
I came up with this solution which works perfectly so far:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output encoding="ISO-8859-1" method="xml" indent="yes" />
<!-- suppress nodes that are not matched -->
<xsl:template match="text() | #*">
<xsl:apply-templates select="text() | #*"/>
</xsl:template>
<xsl:template match="/">
<xsl:choose>
<xsl:when test="not(ExportData/DataSet/Tables/Table/Rows/node())">
<xsl:message terminate="yes">No line items</xsl:message>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="/ExportData/DataSet/Tables/Table/Rows">
<INVOIC02>
<!-- apply LINE ITEMS template -->
<xsl:apply-templates select="R"/>
</INVOIC02>
</xsl:template>
<!-- Template creating LINE ITEMS -->
<xsl:template match="R">
<Line>
<elements></elements>
</Line>
</xsl:template>
</xsl:stylesheet>
If there are <R> elements the output is this:
<?xml version="1.0" encoding="ISO-8859-1"?>
<INVOIC02>
<Line>
<elements/>
</Line>
<Line>
<elements/>
</Line>
<Line>
<elements/>
</Line>
<Line>
<elements/>
</Line>
<Line>
<elements/>
</Line>
</INVOIC02>
If there is just <Rows/> and no <R>s the mapping is aborted.
Now I have two questions:
-Is my test for <R> elements robust: test="not(ExportData/DataSet/Tables/Table/Rows/node())" ?
-I am using <xsl:apply-templates> to create the <Line> items instead of an <xsl:for-each> construct. Are my XPath expressions okay or could I make them better?

Is my test for elements robust: test="not(ExportData/DataSet/Tables/Table/Rows/node())"
?
Well, do you want it to fail if there are no R elements, or fail if Rows does not have a child node(), which would include any element (not just R), text(), comment() or processing-instruction()?
If you really want to verify that there is at least one R element that is a child of Rows, you should adjust the test criteria to be more specific:
test="not(ExportData/DataSet/Tables/Table/Rows/R)"
Otherwise, it may pass that test and continue processing and not generate the content you want.
I am using <xsl:apply-templates> to create the <Line> items instead of an
<xsl:for-each> construct.
Are my XPath expressions okay or could I make them better?
You could get rid of the <xsl:if> conditional logic inside of your template for the root node and move that logic into a template for Rows that don't contain R children. Putting logic into xsl:template #match criteria makes it easier for XSLT processors to optimize, which can lead to performance gains.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output encoding="ISO-8859-1" method="xml" indent="yes" />
<!-- suppress nodes that are not matched -->
<xsl:template match="text() | #*">
<xsl:apply-templates select="text() | #*"/>
</xsl:template>
<!--All Rows must contain an R.
If we encounter any that do not, terminate the transform -->
<xsl:template match="/ExportData/DataSet/Tables/Table/Rows[not(R)]">
<xsl:message terminate="yes">No line items</xsl:message>
</xsl:template>
<!--match for Rows that have R children -->
<xsl:template match="/ExportData/DataSet/Tables/Table/Rows[R]">
<INVOIC02>
<!-- apply LINE ITEMS template -->
<xsl:apply-templates select="R"/>
</INVOIC02>
</xsl:template>
<!-- Template creating LINE ITEMS -->
<xsl:template match="R">
<Line>
<elements></elements>
</Line>
</xsl:template>
</xsl:stylesheet>

This will check whether R node has child element:
<xsl:if test="R">
<!--What you want to do here-->
</xsl:if>

Related

How to not touch a record if text exist in an XML field but process the record in no text exist using XSLT 2

Using XSLT 2 how can I skip and not touch a record if a field contains text, in this case a date? I want to only process all the record that don't have a <SurveyDate> and don't touch record that already have a <SurveyDate>.
I tried using a choose statement with a test of "not(SurveyDate/text())" but this is not working. here is my complete XSL code:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:lookup="lookup" xmlns:exsl="http://exslt.org/common" exclude-result-prefixes="lookup exsl">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes" encoding="utf-8" media-type="xml/plain" />
<xsl:strip-space elements="*" />
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
<xsl:template match="Sub">
<!-- This is the final output -->
<xsl:choose>
<xsl:when test="not(SurveyDate/text())">
<xsl:if test= "count(Request/Phase/Status) = count(Request/Phase/Status[matches(. , 'Sup|Ser|Adm|Can')])">
<Request>
<xsl:copy-of select="Request/Code"/>
<SurveyDate>
<xsl:value-of select="format-dateTime(current-dateTime(), '[Y0001]-[M01]-[D01]T[H1]:[m01]:[s01]')"/>
</SurveyDate>
</Request>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<!-- just for testing remove when done -->
<Test>Do nothing</Test>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
And this is my test XML data.
<?xml version='1.0' encoding='UTF-8'?>
<document>
<businessobjects>
<Sub>
<Code>1.02</Code>
<Status>UsrWorkOrderCancelled</Status>
<Request>
<Code>1.00</Code>
<Description>Test 1</Description>
<SurveyDate>2022-11-02T22:55:55</SurveyDate>
<Phase>
<Code>1.01</Code>
<Status>UsrWorkOrderSupervisorApproved</Status>
</Phase>
<Phase>
<Code>1.02</Code>
<Status>UsrWorkOrderCancelled</Status>
</Phase>
</Request>
</Sub>
<Sub>
<Code>2.01</Code>
<Status>UsrWorkOrderSupervisorApproved</Status>
<Request>
<Code>2.00</Code>
<Description>Test 2</Description>
<SurveyDate></SurveyDate>
<Phase>
<Code>2.01</Code>
<Status>UsrWorkOrderSupervisorApproved</Status>
</Phase>
<Phase>
<Code>2.02</Code>
<Status>UsrWorkOrderCancelled</Status>
</Phase>
</Request>
</Sub>
</businessobjects>
</document>
The result XML I need is this:
<document>
<businessobjects>
<Request>
<Code>2.00</Code>
<SurveyDate>2022-11-03T21:45:13</SurveyDate>
</Request>
</businessobjects>
</document>
My advice: forget using xsl:choose or xsl:if, and instead put the conditional logic into the template's match expression:
<xsl:template match="Sub[not(Request/SurveyDate/text())]">
<!-- handle Sub without SurveyDate -->
<!-- ... -->
</xsl:template>
Leave the case where a Sub does have a SurveyDate for the identity template to handle, if you want to copy it unchanged. If you want to remove it (it's not clear from your test code what you want to do with it), you could add another template to do so:
<xsl:template match="Sub"/>
Note that template would have a lower priority than the one above, because its match expression is simpler, so it would apply only to Sub elements which did have a SurveyDate descendant.

How to check contain only characters + space and `p` using regex

I want to check to contain only characters + space and <p> nodes inside <used>.
Input:
<root>
<used><p>String 1</p></used>
<used>string 2<p>string 3</p></used>
<used>string 4</used>
<used><image>aaa.jpg</image>para</used>
The output should be:
<ans>
<abc>string 1</abc>
<abc>string 4</abc>
</ans>
Tried code:
<ans>
<abc>
<xsl:template match="root">
<xsl:choose>
<xsl: when test="getCode/matches(text(),'^[a-zA-Z0-9]+$')">
<xsl:text>text()</xsl:text>
</xsl:when>
</xsl:choose>
</xsl:template>
</abc>
</ans>
My tried code is not working as I am expecting. How can I fix this? Thank you. I am using XSLT 2.0
You can use the following XSLT-2.0 stylesheet to get the desired result:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl= "http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<!-- Handle the <root> element -->
<xsl:template match="/root">
<ans>
<xsl:apply-templates select="used" />
</ans>
</xsl:template>
<!-- Create <abc> elements for every matching element -->
<xsl:template match="used[not(*) and matches(text(),'^[\sa-zA-Z0-9]+$')] | used[not(text()) and matches(p/text(),'^[\sa-zA-Z0-9]+$')]/p">
<abc><xsl:copy-of select="text()" /></abc>
</xsl:template>
<!-- Remove all spurious text nodes -->
<xsl:template match="text()" />
</xsl:stylesheet>
Its result is
<?xml version="1.0" encoding="UTF-8"?>
<ans>
<abc>String 1</abc>
<abc>string 4</abc>
</ans>

xslt: move all siblings inside the first one

I've searched through similar questions, but couldn't make any of the suggestions to work. I have the following xml I need to modify it
<XDB>
<ROOT>
<KEY><ID>12345</ID><DATE>5/10/2011</DATE></KEY>
<PERSONAL><ID>1</ID><INFO><LASTNAME>Smith</LASTNAME>...</INFO></PERSONAL>
<CONTACT><ID>1</ID><EMAIL>asmith#yahoo.com</EMAIL>...</CONTACT>
</ROOT>
<ROOT>
<KEY><ID>98765</ID><DATE>5/10/2013</DATE></KEY>
<CONTACT><ID>2</ID><EMAIL>psmithton#yahoo.com</EMAIL>...</CONTACT>
</ROOT>
...
</XDB>
And it needs to look like this:
<XDB>
<ROOT>
<KEY><ID>12345</ID><DATE>5/10/2011</DATE>
<PERSONAL><ID>1</ID><INFO><LASTNAME>Smith</LASTNAME>...</INFO></PERSONAL>
<CONTACT><ID>1</ID><EMAIL>asmith#yahoo.com</EMAIL>...</CONTACT>
</KEY>
</ROOT>
<ROOT>
<KEY><ID>98765</ID><DATE>5/10/2013</DATE>
<CONTACT><ID>2</ID><EMAIL>psmithton#yahoo.com</EMAIL>...</CONTACT>
</KEY>
</ROOT>
...
</XDB>
I need to make 2...n siblings as children of the first 'key' sibling. Essentially, i need to remove the closing < /KEY> and put it before the closing < /ROOT>. I would appreciate your help.
Thanks.
Following xslt based on Identity transform could make this job
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!-- Copy everything you find... -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<!-- ... but if you find first element inside ROOT ... -->
<xsl:template match="ROOT/node()[1]">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
<!-- ... copy its sibling into it ... -->
<xsl:copy-of select="following-sibling::*" />
</xsl:copy>
</xsl:template>
<!-- ignore other elements inside ROOT element since they are copied in template matching first element -->
<xsl:template match="ROOT/node()[position() > 1]" />
</xsl:stylesheet>

How to instruct XSLT to apply template only on children?

When applyng this XSLT:
<xsl:template match="e">
<xsl:value-of select="#name"/>
</xsl:template>
To this xml:
<root>
<e name="1"/>
<la>
<e name="bla"/>
</la>
</root>
I get both "1" and "bla".
Why is this so?
How can I make sure that the XSLT is applied only to the direct children of root?
Did you try match="root/e"? If you want to match nodes in a certain context, you need to provide the context in the rule, otherwise all nodes with the matching node name apply to the rule.
You may also use something like:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">
<xsl:apply-templates select="child::e"/>
</xsl:template>
<xsl:template match="e">
<xsl:value-of select="#name"/>
</xsl:template>
</xsl:stylesheet>

Is it possible to make xsl:template sensitive to the pushed-from node without using xsl:param?

I'm pretty sure the answer to this is no, but since the only alternative is what I deem inelegant code, I thought I'd throw this out and see if I'm missing something while hoping this hasn't been asked.
Given this source XML:
<root>
<p>Hello world</p>
<move elem="content" item="test"/>
<p>Another text node.</p>
<content item="test">I can't <b>figure</b> this out.</content>
</root>
I want this result:
<root>
<block>Hello world</block>
<newContent>I can't <hmmm>figure</hmmm> this out.</newContent>
<block>Another text node.</block>
</root>
An ordinary language description:
Replace <move .../> with the result of processing
the element whose name matches move's #elem attribute and whose #item
matches move's #item attribute (e.g., in this case the content of the element [<content>] is processed so <b> is replaced by <hmm>).
Prevent the element from step 1 from
being written out to the result tree in its original document order
The problem is the input XML document will be considerably more complex and variable. And the stylesheet is a third-party transform that I am extending. The template I'd have to copy in order to use a mode-based solution is pretty significant in size and that seems inelegant to me. I know, for example, this would work:
<xsl:template match="b">
<hmmm>
<xsl:apply-templates/>
</hmmm>
</xsl:template>
<xsl:template match="p">
<block>
<xsl:apply-templates/>
</block>
</xsl:template>
<xsl:template match="move">
<xsl:variable name="elem" select="#elem"/>
<xsl:variable name="item" select="#item"/>
<xsl:apply-templates select="//*[name()=$elem and #item=$item]" mode="copy-and-process"/>
</xsl:template>
<xsl:template match="content"/>
<xsl:template match="content" mode="copy-and-process">
<newContent><xsl:apply-templates/></newContent>
</xsl:template>
What I would like to do is have the <xsl:template> that matches "content" be sensitive to what node pushes to it. So, that I can have an <xsl:template match="content"/> that is only executed (and therefore its matching node and children are suppressed) when the node pushed from is <root> and not <move>. The virtue in this is that if the third-party stylesheet's relevant template is updated, I don't have to worry about updating a copy of the stylesheet that processes the <content> node. I'm pretty sure this isn't possible, but I thought it was worth asking about.
Simply do:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kMover" match="move" use="concat(#elem,'+',#item)"/>
<xsl:key name="kToMove" match="*" use="concat(name(),'+',#item)"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="move">
<newContent>
<xsl:apply-templates mode="move" select=
"key('kToMove', concat(#elem,'+',#item))/node()"/>
</newContent>
</xsl:template>
<xsl:template match="p">
<block><xsl:apply-templates/></block>
</xsl:template>
<xsl:template match="b" mode="move">
<hmmm><xsl:apply-templates/></hmmm>
</xsl:template>
<xsl:template match="*[key('kMover', concat(name(),'+',#item))]"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<root>
<p>Hello world</p>
<move elem="content" item="test"/>
<p>Another text node.</p>
<content item="test">I can't <b>figure</b> this out.</content>
</root>
the wanted, correct result is produced:
<root>
<block>Hello world</block>
<newContent>I can't <hmmm>figure</hmmm> this out.</newContent>
<block>Another text node.</block>
</root>