I have the following code which I use to get the sum of product values.
<xsl:for-each select="//fn:array[#key= 'Items']/fn:map[*[#key = 'OrderId'][.= $OrderId ] and *[#key = 'Type'][.= 'ServiceRevenue' ] ] ">
<amount>
<xsl:value-of select="fn:map[#key ='Amount']/*[#key = 'Net']" />
</amount>
</xsl:for-each>
I am trying to only calculate the first occurrence where [*[#key = 'OrderId'][.= $OrderId ].
I tried the following, but I am still getting all the occurrences:
<xsl:for-each select="//fn:array[#key= 'Items']/fn:map[*[#key = 'OrderId'][.= $OrderId ][0] and *[#key = 'Type'][.= 'ServiceRevenue' ] ] ">
<amount>
<xsl:value-of select="fn:map[#key ='Amount']/*[#key = 'Net']" />
</amount>
</xsl:for-each>
Sample JSON:
[
{
"Items":[
{
"Id":"5b44f410-7034-4972-919d-acac00ead7e9",
"OrderId":"e7791603-ca5f-47c4-9b0c-acac00ead753",
"Type":"ServiceRevenue",
"Amount":{
"Value":25.0,
"Net":25.0,
"Tax":0.0
}
}
]
},
{
"Items":[
{
"Id":"5b44f410-7034-4972-919d-acac00ead7e9",
"OrderId":"e7791603-ca5f-47c4-9b0c-acac00ead753",
"Type":"ServiceRevenue",
"Amount":{
"Value":25.0,
"Net":25.0,
"Tax":0.0
}
}
]
}
]
I only want to calculate the Amount of the first Item occurrence where the OrderId = 'e7791603-ca5f-47c4-9b0c-acac00ead753'
Related
I have a large XML file with many request, within each request there may be multiple orders. I want to only output the request if all its orders have a certain status that indicate its been completed.
The Status <Stat> I want are Stat03, Stat04, Stat05 and Stat06.
My XML looks like this:
<?xml version='1.0' encoding='UTF-8'?>
<document>
<BO>
<MainReq>
<UR>
<ReqNum>REQ001.00</ReqNum>
<Description>Description 01</Description>
<PH>
<PN>REQ001.01</PN>
<Stat>Stat05</Stat>
</PH>
<PH>
<PN>REQ001.02</PN>
<Stat>Stat04</Stat>
</PH>
<PH>
<PN>REQ001.03</PN>
<Stat>Stat06</Stat>
</PH>
</UR>
</MainReq>
<MainReq>
<UR>
<ReqNum>REQ002.00</ReqNum>
<Description>Description 02</Description>
<PH>
<PN>REQ002.01</PN>
<Stat>Stat05</Stat>
</PH>
<PH>
<PN>REQ002.02</PN>
<Stat>Stat04</Stat>
</PH>
<PH>
<PN>REQ002.03</PN>
<Stat>Stat03</Stat>
</PH>
<PH>
<PN>REQ002.04</PN>
<Stat>Stat06</Stat>
</PH>
<PH>
<PN>REQ002.05</PN>
<Stat>Stat01</Stat>
</PH>
</UR>
</MainReq>
</BO>
</document>
<MainReq> contains the request and within it there can be multiple orders <PH>
The desire output would be this, since the only request that meets the criteria is REQ001.
REQ002 has one <PH> that is in Stat01 so it does not qualify.
<document>
<BO>
<IfReq>
<ReqNum>REQ001</ReqNum>
<Date>2022-07-21+02:00</Date>
</IfReq>
</BO>
</document>
My solution was to count all the <PH> and then count ONLY the <Stat> that are in any of the these states, Stat03, Stat04, Stat05 and Stat06. Once I have those values I can compare them and if they are equal I output the request number and date. A <MainReq> does not have to have all of those states but I only want to count the <PH> that are in that set of states to compare with the total count of <PH>.
Edit to add more clarity..
My 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="MainReq">
<xsl:variable name="subTotalCount">
<xsl:value-of select="count(UR/PH/PN)" />
</xsl:variable>
<xsl:variable name="subCompletedCount">
<xsl:choose>
<xsl:when test="UR/PH/Stat = 'Stat03' and 'Stat04' and 'Stat05' and 'Stat06'">
<xsl:value-of select="sum(count(UR/PH/Stat) )" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'0'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- This is the final output -->
<xsl:if test= "$subTotalCount = $subCompletedCount">
<IfReq>
<xsl:copy-of select="UR/ReqNum"/>
<Date>
<xsl:value-of select="current-date()"/>
</Date>
</IfReq>
</xsl:if>
<!-- For debugging -->
<Req>
<xsl:copy-of select="UR/ReqNum"/>
</Req>
<sub>
<TotalCount>
<xsl:value-of select="$subTotalCount"/>
</TotalCount>
<compCount>
<xsl:value-of select="$subCompletedCount"/>
</compCount>
<xsl:copy-of select="UR/PH/PN"/>
</sub>
</xsl:template>
</xsl:stylesheet>
You could use this:
<!-- This is the final output -->
<xsl:if test="count(UR/PH/Stat) = count(UR/PH/Stat[matches(. , 'Stat0[3456]')])">
<IfReq>
<xsl:copy-of select="UR/ReqNum"/>
<Date>
<xsl:value-of select="current-date()"/>
</Date>
</IfReq>
</xsl:if>
It uses count on matches() in the predicate to count the wanted Stat and compares is with total count of Stat
Your explanation is a bit difficult to follow, but I think you are asking for
//MainReq[every $s in .//Stat satisfies $s = ('Stat03', 'Stat04', 'Stat05', 'Stat06')]
Use this one-liner XPath 2.0 expression:
//MainReq[not(.//Stat[not(. = ('Stat03', 'Stat04', 'Stat05', 'Stat06'))])]
XSLT-based verification:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy-of select="//MainReq[not(.//Stat[not(. = ('Stat03', 'Stat04', 'Stat05', 'Stat06'))])]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<document>
<BO>
<MainReq>
<UR>
<ReqNum>REQ001.00</ReqNum>
<Description>Description 01</Description>
<PH>
<PN>REQ001.01</PN>
<Stat>Stat05</Stat>
</PH>
<PH>
<PN>REQ001.02</PN>
<Stat>Stat04</Stat>
</PH>
<PH>
<PN>REQ001.03</PN>
<Stat>Stat06</Stat>
</PH>
</UR>
</MainReq>
<MainReq>
<UR>
<ReqNum>REQ002.00</ReqNum>
<Description>Description 02</Description>
<PH>
<PN>REQ002.01</PN>
<Stat>Stat05</Stat>
</PH>
<PH>
<PN>REQ002.02</PN>
<Stat>Stat04</Stat>
</PH>
<PH>
<PN>REQ002.03</PN>
<Stat>Stat03</Stat>
</PH>
<PH>
<PN>REQ002.04</PN>
<Stat>Stat06</Stat>
</PH>
<PH>
<PN>REQ002.05</PN>
<Stat>Stat01</Stat>
</PH>
</UR>
</MainReq>
</BO>
</document>
The Xpath expression is evaluated, and the result of this evaluation (all selected nodes) is sent to the output:
<MainReq>
<UR>
<ReqNum>REQ001.00</ReqNum>
<Description>Description 01</Description>
<PH>
<PN>REQ001.01</PN>
<Stat>Stat05</Stat>
</PH>
<PH>
<PN>REQ001.02</PN>
<Stat>Stat04</Stat>
</PH>
<PH>
<PN>REQ001.03</PN>
<Stat>Stat06</Stat>
</PH>
</UR>
</MainReq>
Explanation:
For a full explanation, see: https://stackoverflow.com/a/58792165/36305
I have a large json message and, I want to change some fields values only. Rest of fields should be sent exactly same as input message. Without hardcoding each item in xslt, is there a way to change selected items only?
XML input payload
<?xml version="1.0" encoding="UTF-8"?>
<root>
<glossary>
<title>example glossary</title>
<GlossDiv>
<GlossList>
<GlossEntry>
<Abbrev>ISO 8879:1986</Abbrev>
<Acronym>SGML</Acronym>
<GlossDef>
<GlossSeeAlso>
<element>GML</element>
<element>XML</element>
</GlossSeeAlso>
<para>A meta-markup language, used to create markup languages such as DocBook.</para>
</GlossDef>
<GlossSee>markup</GlossSee>
<GlossTerm>Standard Generalized Markup Language</GlossTerm>
<ID>SGML</ID>
<SortAs>SGML</SortAs>
</GlossEntry>
</GlossList>
<title>S</title>
</GlossDiv>
</glossary>
</root>
Sample Payload
{
"glossary": {
**"title": "example glossary",**
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Markup Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": ["GML", "XML"]
},
"GlossSee": "markup"
}
}
}
}
}
Expected output
{
"glossary": {
**"title": "New value",**
"GlossDiv": {
"title": "S",
"GlossList": {
"GlossEntry": {
"ID": "SGML",
"SortAs": "SGML",
"GlossTerm": "Standard Generalized Markup Language",
"Acronym": "SGML",
"Abbrev": "ISO 8879:1986",
"GlossDef": {
"para": "A meta-markup language, used to create markup languages such as DocBook.",
"GlossSeeAlso": ["GML", "XML"]
},
"GlossSee": "markup"
}
}
}
}
}
I have bold the changing fields
In terms of XML to XML that is basic XSLT
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="glossary/title">
<xsl:copy>New Value</xsl:copy>
</xsl:template>
</xsl:stylesheet>
using the identity transformation template plus additional templates for the elements or attributes (or nodes in general) you want to transform.
Transforming to JSON is a different issue.
I have a data element that can either be a Map or a List of Maps.
Here are two simplified data examples (expressed as JSON for readability)
1) Maps
{
"lom:general": {
"lom:title": {"lom:string": {
"language": "fr",
"content": "Premiers Pas avec un Progiciel de Gestion Intégré"
}},
"lom:language": "fr"
},
"lom:technical": {"lom:location": "http://hub11.ecolearning.eu/course/progiciel-metiers-pgi/"}
}
2) List of Maps
{
"lom:general": {
"lom:title": {"lom:string": [
{
"language": "fr",
"content": "DIY Education aux médias et à l'information - 3ed"
},
{
"language": "en",
"content": "DIY Media and Information Literacy - 3ed"
}
]},
"lom:language": [
"fr",
"en"
]
},
"lom:technical": {"lom:location": "http://hub5.ecolearning.eu/course/diy-do-it-yourself/"}
}
I want to check the type of element lom:general.lom:title.lom:string and iterate it if it is a list.
I have tried the following:
<title>
<#assign titles = lom\:general.lom\:title.lom\:string>
<#if titles?is_enumerable>
<#list titles as title>
<string language="${title.language}">${title.content}</string>
</#list>
<#else>
<string language="${titles.language}">${titles.content}</string>
</#if>
</title>
The template works as expected with the first data example. However, the second data example raises a StackOverflowError
java.lang.StackOverflowError
at sun.reflect.misc.ReflectUtil.checkPackageAccess(ReflectUtil.java:188)
at sun.reflect.misc.ReflectUtil.checkPackageAccess(ReflectUtil.java:164)
at sun.reflect.generics.reflectiveObjects.TypeVariableImpl.getGenericDeclaration(TypeVariableImpl.java:164)
at sun.reflect.generics.reflectiveObjects.TypeVariableImpl.equals(TypeVariableImpl.java:189)
at com.sun.beans.TypeResolver.resolve(TypeResolver.java:190)
at com.sun.beans.TypeResolver.resolve(TypeResolver.java:201)
Where the last line is repeated dozens of times
Is there any way to get this to sort in the order of the select statement when it finds one or more PIDs?
<xsl:apply-templates select="td:Benutzer_zu_POI[
td:PID = '400639' or
td:PID = '400929' or
td:PID = '401184' or
td:PID = '401006' or
td:PID = '430003408' or
td:PID = '401519' or
td:PID = '400660' or
td:PID = '500287' or
td:PID = '200461' or
td:PID = '400756']">
Many thanks for any help!
You can, but it's not as automatic as you may be hoping. It would go something like this:
<xsl:apply-templates select="td:Benutzer_zu_POI[
td:PID = '400639' or
td:PID = '400929' or
td:PID = '401184' or
td:PID = '401006' or
td:PID = '430003408' or
td:PID = '401519' or
td:PID = '400660' or
td:PID = '500287' or
td:PID = '200461' or
td:PID = '400756']">
<xsl:sort select="string-length(
substring-before(
'|400639|400929|401184|401006|...|400756|',
concat('|', td:PID, '|')))"
data-type="number" />
</xsl:apply-templates>
To explain how this works, the intent is to produce smaller numbers as a result of the select, depending on the item's order. So for example, for 400639, this evaluates to:
string-length(
substring-before(
'|400639|400929|401184|401006|...|400756|',
'|400639|'))
string-length('')
0
for 401184, it's
string-length(
substring-before(
'|400639|400929|401184|401006|...|400756|',
'|401184|'))
string-length('|400639|400929')
14
and so on.
The purpose of the concat() is to delimit the value in pipe symbols to prevent partial matches. If we didn't use the concat(), you might have a situation like this:
(assume td:PID = 456)
string-length(
substring-before(
'|123456|234567|34567|456|',
'456'))
string-length(
'|123')
4
meaning that 456 would be placed before 23456 and 34567 when it should actually be after them. When it's surrounded in delimiters, this doesn't happen:
string-length(
substring-before(
'|123456|234567|34567|456|',
'|456|'))
string-length(
'|123456|234567|34567')
20
Would this do the trick?
<xsl:apply-templates select="td:Benutzer_zu_POI[td:PID = '400639']/>
<xsl:apply-templates select="td:Benutzer_zu_POI[td:PID = '401184']/>
<xsl:apply-templates select="td:Benutzer_zu_POI[td:PID = '401006']/>
<xsl:apply-templates select="td:Benutzer_zu_POI[td:PID = '430003408']/>
<xsl:apply-templates select="td:Benutzer_zu_POI[td:PID = '401519']/>
<xsl:apply-templates select="td:Benutzer_zu_POI[td:PID = '400660']/>
<xsl:apply-templates select="td:Benutzer_zu_POI[td:PID = '500287']/>
<xsl:apply-templates select="td:Benutzer_zu_POI[td:PID = '200461']/>
<xsl:apply-templates select="td:Benutzer_zu_POI[td:PID = '400756']/>
(Whether it's equivalent or not depends on the details of your data, which you haven't shown us. Your version will only display each Benutzer once even if it matches several of the PIDs.).
I realize this code isn't the cleanest, but properly refactoring it unfortunately isn't an option.
The issue is that I would expect position() on the second iteration to return a true value. But when using position() it never returns true, as expected on the second iteration.
But if I hard code the selection values, it returns the expected result. Here is an example:
<root>
<MainProducts>
<MainProduct>
<Published>0</Published>
</MainProduct>
<MainProduct>
<Published>1</Published>
</MainProduct>
</MainProducts>
<SubProducts>
<SubProduct>
<IsDefault>1</IsDefault>
</SubProduct>
<SubProduct>
<IsDefault>0</IsDefault>
</SubProduct>
</SubProducts>
</root>
XML content:
<xsl:for-each select="/root/SubProducts/SubProduct">
<script type="text/javascript">
// Always returns false
console.log("Dynamic position " + <xsl:value-of select="position()" /> + " IsDefault: " + ( <xsl:value-of select="/root/MainProducts/MainProduct[position()]/Published" /> == 1 ? "true" : "false" ));
</script>
</xsl:for-each>
<script type="text/javascript">
// Returns false, but expected to return true on second iteration.
console.log("Hard coded position 1 IsDefault: " + ( <xsl:value-of select="/root/MainProducts/MainProduct[1]/Published" /> == 1 ? "true" : "false" ));
// Returns true
console.log("Hard coded position 2 IsDefault: " + ( <xsl:value-of select="/root/MainProducts/MainProduct[2]/Published" /> == 1 ? "true" : "false" ));
</script>
Here is the exact console output:
Dynamic position 1 IsDefault: false
Dynamic position 2 IsDefault: false
Hard coded position 1 IsDefault: false
Hard coded position 2 IsDefault: true
What am I missing here that position() isn't selecting the node properly?
<script type="text/javascript">
// Always returns false
console.log("Dynamic position " +
<xsl:value-of select="position()" /> +
" IsDefault: " +
( <xsl:value-of select="/root/MainProducts/MainProduct[position()]/Published" />
== 1 ? "true" : "false" ));
</script>
In the above code MainProduct[position()] is equivalent to MainProduct[true()] )don't forget that position() is context-sensitive!
So, you are actually evaluating:
<xsl:value-of select="/root/MainProducts/MainProduct/Published" />
and this outputs always the string value of the first Published element that is selected by the XPath expression -- it happens to be 0.
Correct code:
<xsl:variable name="vPos" select="position()"/>
<script type="text/javascript">
// Always returns false
console.log("Dynamic position " +
<xsl:value-of select="position()" /> +
" IsDefault: " +
( <xsl:value-of select=
"/root/MainProducts/MainProduct[position()=$vPos]/Published" />
== 1 ? "true" : "false" ));
</script>
Here is a complete transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:for-each select="/*/SubProducts/SubProduct">
<xsl:variable name="vPos" select="position()"/>
<script type="text/javascript">
console.log("Dynamic position " +
<xsl:value-of select="position()" /> +
" IsDefault: " +
( <xsl:value-of select=
"/*/MainProducts/MainProduct
[position()=$vPos]/Published" />
== 1 ? "true" : "false" ));
</script>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<root>
<MainProducts>
<MainProduct>
<Published>0</Published>
</MainProduct>
<MainProduct>
<Published>1</Published>
</MainProduct>
</MainProducts>
<SubProducts>
<SubProduct>
<IsDefault>1</IsDefault>
</SubProduct>
<SubProduct>
<IsDefault>0</IsDefault>
</SubProduct>
</SubProducts>
</root>
the wanted, correct result is produced:
<script type="text/javascript">
console.log("Dynamic position " +
1 +
" IsDefault: " +
( 0
== 1 ? "true" : "false" ));
</script>
<script type="text/javascript">
console.log("Dynamic position " +
2 +
" IsDefault: " +
( 1
== 1 ? "true" : "false" ));
</script>