Any way to access the value from xml - xslt-2.0

I'm trying to access values from my xml file using "title" xpath without using /Para000_* because _1,_2 is dynamic.
Is there any way to access the value without using para number please help me
I need value only "Mango" but i'm getting "Mango Orange" values
I have an xml as follows:
<Content code="" title="Food" type="Fruits" paraCode="2">
<props>
<para001_2 title="F1">Mango</para001_2>
<para002_2 title="F2">Grape</para002_2>
</props>
</Content>
<Content code="" title="Food" type="Fruits" paraCode="2">
<props>
<para001_2 title="F1">Orange</para001_2>
<para002_2 title="F2">Grape</para002_2>
</props>
</Content>
tried XSLT as follows:
<xsl:variable name="FruitName" select="/Content/props/*[#title = 'F1']"/>
Result:
Mango

Use this
<xsl:variable name="FruitName" select="/Content/props/*[#title = 'F1'][not(preceding::Content/props/*[#title = 'F1'])]"/>

Please mention on what basis/condition you want access the node <Content>
For the current input given, to access the first node's title
The solution can be:
<xsl:value-of select="root/Content/props/*[1][#title = 'F1']"/>

Related

Create XPath with dynamic variable in runtime (XSL version 1.0) -> then use it as select

I have already read some pages here about this topic but nothing matched my issue so far.
So my problem is that I need to create an xpath string dynamically and use that xpath string in the select of a "<xsl:value-of"-tag to select the appropriate value from the xml document. But instead it is showing the string of the xpath itself as shown below
So I have a simple XML Document (just a small example to show the issue. The original is much larger containing more fields)
<s0:RootNode>
<s0:HEADER>
<s0:DocumentDate>2022-10-13</s0:DocumentDate>
<s0:DocumentID>123456</s0:DocumentID>
</s0:HEADER>
</s0:RootNode>
Here I create the xPath string dynamically:
<!-- This ('DocumentID') will be a dynamic value later on -->
<xsl:variable name="varField" select="'DocumentID'"/>
<!-- The output of that variable is the correct xPath 's0:HEADER/s0:DocumentID/text()' I want to use. -->
<xsl:variable name="xPath" select="concat('s0:HEADER/s0:',$varField,'/text()')" />
So my map
<Value>
<xsl:value-of select="$xPath" />
</Value>
Will produce the output:
<Value>s0:HEADER/s0:DocumentID/text()</Value>
instead of:
<Value>123456</Value>
So how can I 'force' to select the value 123456 based on the generic xpath string instead of the xPath string itself?
Many thanks for your help.
You could just make the element name variable. You might not need s0:RootNode. Just adjust based on your context.
<!-- This ('DocumentID') will be a dynamic value later on -->
<xsl:variable name="varField" select="'DocumentID'"/>
<xsl:variable name="test">
<Value>
<xsl:value-of select="s0:RootNode/s0:HEADER/s0:*[local-name() = $varField]" />
</Value>
</xsl:variable>

why am I getting Required cardinality of value of variable $depts is exactly one; supplied value has cardinality more than one

Trying to figure out some homework here and the online teacher has never responded to any questions I ask.
I keep getting an error when I try to process the xml document.
"XTTE0570: Required cardinality of value of variable $depts is exactly one; supplied value has cardinality more than one"
The case problem instructions:
First, create a template named getemployees.
Within the getEmployees template, create a variable named depts containing a sequence of the following text strings representing the department codes for Lucy’s sample data: ‘a00’, ‘c01’, ‘d11’, ‘d21’, ‘e11’, and ‘e21’.
After the line to create the depts variable, create the departments element.
Within the departments element, insert a for-each loop that loops through each entry in the
depts sequence.
For each entry in the depts sequence do the following:
a. Create a variable named currentDept equal to the current item in the depts sequence.
b. Create an element named department with an attribute named deptiD whose value is equal to the value of the currentDept variable.
c. Use the doc() function to reference the “deptcurrent.xml” file, where current is the value of the currentDept variable. (Hint: Use the concat() function to combine the text strings for “dept”, the currentDept variable, and the text string “.xml”.)
d. Use the copy-of element to copy the contents of the employees element and its descendants to the department element.
Save your changes to the file and then use your XSLT 2.0 processor to generate the result
document horizons.xml by applying the getEmployees template within the alldepartments.xsl
style sheet.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template name="getEmployees">
<xsl:variable name="depts" select="('a00', 'c01', 'd11', 'd21', 'e11', 'e21')" as="xs:string" />
<departments>
<xsl:for-each select="$depts">
<xsl:variable name="currentDept">
<xsl:value-of select="." />
</xsl:variable>
<department deptID="{$currentDept}">
<xsl:value-of select="doc(concat('dept',$currentDept, '.xml'))" />
<xsl:copy-of select="employees" />
</department>
</xsl:for-each>
</departments>
</xsl:template>
</xsl:stylesheet>
Should generate something similar to:
<?xml version="1.0" encoding="UTF-8"?>
<departments>
<department dept="a00">
<employees>
<employee empID="10">
<firstName>Marylin</firstName>
<middleInt>A</middleInt>
<lastName>Johnson</lastName>
<department>A00</department>
<phone>3978</phone>
<email>Johnson.60#example.com/horizons</email>
<dateHired>2000-01-01</dateHired>
<title>President</title>
<edLevel>18</edLevel>
<gender>F</gender>
<birthDate>1968-08-24</birthDate>
<salary>121300</salary>
<bonus>2300</bonus>
<commission>9700</commission>
</employee>
<employee empID="40">
<firstName>Heather</firstName>
<middleInt>D</middleInt>
<lastName>Gordon</lastName>
<department>A00</department>
<phone>3915</phone>
<email>Gordon.59#example.com/horizons</email>
<dateHired>2009-03-01</dateHired>
<title>Manager</title>
<edLevel>18</edLevel>
<gender>F</gender>
<birthDate>1986-06-03</birthDate>
<salary>85400</salary>
<bonus>1700</bonus>
<commission>6500</commission>
</employee>
</employees>
</department>
</departments>
If you use the as attribute on xsl:variable to declare the type of your variable then the value you select needs to fit that declaration, so given that you have a sequence of strings you need to use <xsl:variable name="depts" select="('a00', 'c01', 'd11', 'd21', 'e11', 'e21')" as="xs:string*" />.
Additionally the
<xsl:copy-of select="employees" />
inside the for-each over a string sequence doesn't make sense (and explain the error you get after correcting the variable type), there you simply want
<xsl:copy-of select="doc(concat('dept', ., '.xml'))/employees" />

How to map by joining 2 different repeating nodes into a 1 repeating node using BizTalk mapper

The issue is I need to loop over a repeating node but pull info also from a sibling repeating node which matches based on LineId. If this were a database, I would equate each repeating node as a table and want to out put the join of the two tables based on matching ids.
I started out by looping on Root/Shipments/Shipment/Containers/Container to Document/Header/Detail and that seemed to work, since I wanted to create 1 record for every Container from the source as 1 Detail record in the destination. But when I mapped the fields from Root/Shipments/Shipment/Details/Detail, I only got the data from the first record mapped instead of the record with the matching LineId. I then tried adding Root/Shipments/Shipment/Details/Detail to the loop and conditionally suppress Document/Detail where LineId didn't match between the nodes but that didn't work. So, I tried adding a second loop on Detail with same suppress condition and that didn't work either. Then I thought I would try using XSLT Template to "lookup" the data in Root/Shipments/Shipment/Details/Detail based on LineId as an input, but it never returned any data (it was always empty). Also, to fetch each field separately at the field level I would need to search X times, 1 for each field I needed to map instead of 1 time per record, so that seemed inefficient also.
My XSLT knowledge is limited. If possible, I would prefer to use Mapping with script functoids rather than pure XSLT. Performance will be an issue too since the # of files I need to do is very high on this project. The examples I give below are a VERY cut down version of what I need to do with dozens more fields and the source file has several more layers to the hierarchy.
What is the best way to achieve what I need to do?
Source File:
<Root>
<Shipments>
<Shipment>
<ShipId>1</ShipId>
<Details>
<Detail>
<LineId>1</LineId>
<RequestedQty>10</RequestedQty>
<Sku>347</Sku>
<Status>C</Status>
</Detail>
<Detail>
<LineId>2</LineId>
<RequestedQty>5</RequestedQty>
<Sku>125</Sku>
<Status>P</Status>
</Detail>
<Detail>
<LineId>3</LineId>
<RequestedQty>8</RequestedQty>
<Sku>337</Sku>
<Status>O</Status>
</Detail>
<Detail>
<LineId>4</LineId>
<RequestedQty>12</RequestedQty>
<Sku>7438</Sku>
<Status>C</Status>
</Detail>
</Details>
<Containers>
<Container>
<ContainerId>1</ContainerId>
<Details>
<Detail>
<LineId>1</LineId>
<Lot>103</Lot>
<ShipQty>10</ShipQty>
</Detail>
<Detail>
<LineId>2</LineId>
<Lot>102</Lot>
<ShipQty>3</ShipQty>
</Detail>
</Details>
<TrackingNUmber>Z934793498923984</TrackingNUmber>
</Container>
<Container>
<ContainerId>2</ContainerId>
<Details>
<Detail>
<LineId>4</LineId>
<Lot>101</Lot>
<ShipQty>10</ShipQty>
</Detail>
<Detail>
<LineId>4</LineId>
<Lot>105</Lot>
<ShipQty>2</ShipQty>
</Detail>
</Detail>
</Details>
<TrackingNUmber>Z531365161663161</TrackingNUmber>
</Container>
</Containers>
</Shipment>
</Shipments>
</Root>
Into:
<Document>
<Header>
<ShipId>1</ShipId>
<Detail>
<ContainerId>1</ContainerId>
<LineId>1</LineId>
<Lot>103</Lot>
<ShipQty>10</ShipQty>
<RequestedQty>10</RequestedQty>
<Sku>347</Sku>
<Status>C</Status>
<TrackingNUmber>Z934793498923984</TrackingNUmber>
</Detail>
<Detail>
<ContainerId>1</ContainerId>
<LineId>2</LineId>
<Lot>102</Lot>
<ShipQty>3</ShipQty>
<RequestedQty>5</RequestedQty>
<Sku>125</Sku>
<Status>P</Status>
<TrackingNUmber>Z934793498923984</TrackingNUmber>
</Detail>
<Detail>
<ContainerId>2</ContainerId>
<LineId>4</LineId>
<Lot>101</Lot>
<ShipQty>10</ShipQty>
<RequestedQty>12</RequestedQty>
<Sku>7438</Sku>
<Status>C</Status>
<TrackingNUmber>Z531365161663161</TrackingNUmber>
</Detail>
<Detail>
<ContainerId>2</ContainerId>
<LineId>4</LineId>
<Lot>105</Lot>
<ShipQty>2</ShipQty>
<RequestedQty>12</RequestedQty>
<Sku>7438</Sku>
<Status>C</Status>
<TrackingNUmber>Z531365161663161</TrackingNUmber>
</Detail>
</Header>
</Document>
I'm not sure it is the Best solution, but I have something that might get you going. The idea is to create the Detail-element in the target document using a scripting functoid.
Given that you have a lot of elements, I would suggest the following to get the names right:
Create a direct link with all elements you want to appear in the target Detail-element.
"Validate Map" to get the xslt. Copy the Detail-element and its content.
Remove all direct links to the Detail element.
Add a looping functoid between source Container/Details/Detail and target Detail.
Add a scripting functoid, connect it to target Detail. Select Inline XSLT and paste the code you copied in step 2.
Test the map, to verify that most of it works (the Shipment/Details/Detail will still be wrong though)
Modify the xslt in the scripting functoid. You want a variable to point the the Shipment/Details/Detail element corresponding to your current line.
Set variable based on current LineId:
<xsl:variable name="CurrentShipmentDetail" select="../../../../Details/Detail[LineId=current()/LineId]"/>
Modify the elements so you select from the variable
Before:
<RequestedQty>
<xsl:value-of select="../../../../Details/Detail/RequestedQty/text()" />
</RequestedQty>
After:
<RequestedQty>
<xsl:value-of select="$CurrentShipmentDetail/RequestedQty/text()" />
</RequestedQty>
The complete map:
And the complete xslt code:
<Detail>
<ContainerId>
<xsl:value-of select="../../ContainerId/text()" />
</ContainerId>
<LineId>
<xsl:value-of select="LineId/text()" />
</LineId>
<Lot>
<xsl:value-of select="Lot/text()" />
</Lot>
<ShipQty>
<xsl:value-of select="ShipQty/text()" />
</ShipQty>
<xsl:variable name="CurrentShipmentDetail" select="../../../../Details/Detail[LineId=current()/LineId]"/>
<RequestedQty>
<xsl:value-of select="$CurrentShipmentDetail/RequestedQty/text()" />
</RequestedQty>
<Sku>
<xsl:value-of select="$CurrentShipmentDetail/Sku/text()" />
</Sku>
<Status>
<xsl:value-of select="$CurrentShipmentDetail/Status/text()" />
</Status>
<TrackingNUmber>
<xsl:value-of select="../../TrackingNUmber/text()" />
</TrackingNUmber>
</Detail>
You should be able to do this with Looping and the Equal Functoid.
Connect Shipment/Detail to Looping Functoid, then connect Container/Detail to that same Looping Functoid.
Then connect Shipment//LineId and Container//LindId to an Equal Functoid then connect the Equal Functoid to the targed Detail.
You'll see how the Mapper builds the loops and conditions in the xsl. You can adjust from there.

xpath expression to select specific xml nodes that are available in a file

I was trying to find the out a way for my strange problem.
How to write an xpath to select specific xml nodes that are available in another text file.
For Instance,
<xsl:for-each select="SUBSCRIBER_PROFILE_LIST/SUBSCRIBER_PROFILE_INFO[GROUP_NAME eq (group name list in a text file as input)]">
For example,
<xsl:for-each select="SUBSCRIBER_PROFILE_LIST/SUBSCRIBER_PROFILE_INFO[GROUP_NAME eq collection('select_nodes.txt')]">
select_nodes.txt contains list of string that can be selected only
For example
ABC
IJK
<SUBSCRIBER>
<MSISDN>123456</MSISDN>
<SUBSCRIBER_PROFILE_LIST>
<SUBSCRIBER_PROFILE_INFO>
<PROFILE_MSISDN>12345</PROFILE_MSISDN>
<GROUP_NAME>ABC</GROUP_NAME>
<GROUP_ID>18</GROUP_ID>
</SUBSCRIBER_PROFILE_INFO>
<SUBSCRIBER_PROFILE_INFO>
<PROFILE_MSISDN>456778</PROFILE_MSISDN>
<GROUP_NAME>DEF</GROUP_NAME>
<GROUP_ID>100</GROUP_ID>
</SUBSCRIBER_PROFILE_INFO>
<SUBSCRIBER_PROFILE_INFO>
<PROFILE_MSISDN>78876</PROFILE_MSISDN>
<GROUP_NAME>IJK</GROUP_NAME>
<GROUP_ID>3</GROUP_ID>
</SUBSCRIBER_PROFILE_INFO>
</SUBSCRIBER>
XSLT2 has limited functionality for parsing arbitrary text files. I would suggest:
Make the select_nodes.txt an XML file and load it using the doc() function:
<xsl:variable name="group_names" as="xs:string *"
select="doc('select_nodes.xml')/groups/group"/>
with select_nodes.xml looking like this:
<?xml version="1.0" encoding="UTF-8"?>
<groups>
<group>ABC</group>
<group>IJK</group>
</groups>
Pass the group names as a stylesheet parameter. (How you do this depends on which XSLT engine you're using and whether it's through the command line or an API.) If it's through an API, then you may be able to pass the values in directly as xs:string-typed objects. Otherwise you'll have to parse the parameter:
<xsl:param name="group_names_param"/>
<!-- Assuming the input string is a whitespace-separated list of names -->
<xsl:variable name="group_names" as="xs:string *"
select="tokenize($group_names_param, '\s+')"/>
In either case your for-each expression would then look like this:
<xsl:for-each select="
SUBSCRIBER_PROFILE_LIST/SUBSCRIBER_PROFILE_INFO[GROUP_NAME = $group_names]">
<!-- Do something -->
</xsl:for-each>

xslt how to read the document-node()

I have a xml file in which one of the element has the CDATA as the value. I put the CDATA value into a variable which I can see is value type of document-node(1) when i debug my code from oXygen. How do I iterate the document-node()?
copy can give me a new xml file. but what I need is not a new file. I only need to read certain nodes and generate a report based on the values on those nodes. so I directly copy the CDATA to my variable and thought I can manipulate it.
I tried to use substring to read the variable things but failed.
I tried to use document(variable) to open the variable but Oxygen give me the debug-error of FODC0002:I/O error reported by xml parser processing file.
here the file is my variable which looks like a xml file
I did google search for the error but only got bench of non-closed questions like Oxygen throw I/O error when use document().
Would anybody let me know what's going wrong? or give me a better solution?
I also tried parse-xml() but I got the following error from Saxon:
F[Saxon-EE9.5.1.5] the processing instruction target matching "[xX][mM][lL]" is not allowed
F[Saxon-EE9.5.1.5] FODC0006: First argument to parse-xml() is not a well formed and namespace-well-formed XML document.
my code to use parse-xml is as below:
<xsl:template match="data"
<xsl:for-each select="parse-xml(root/outsideData)//nodeLevel1/nodeLevel2">
Could anyone give me a sample about how to use parse-xml()? I did google search but didn't find useful samples.
Thanks very much!
A piece of my data is like the following:
<root>
<outsideData id="123">
<child1 key="124375438"/>
<![CDATA[ <?xml version=1.0 encoding="UTF-8"?><insideData xmlns:xlink="http://www.w3.org/1999/xlink">
<nodeLevel1>
<nodeLevel21>packing</nodeLevel21>
<nodeLevel22 ref="12343-454/560" xlink:href="URN:X-MN:DD%3FM=B888%26SDC=A%26CH=79% .../>
</nodeLevel1>
]]>
</outsideData>
</root>
I want to get the inside CDATA <nodeLevel22> #ref and #xlink which will get DD-FM-B888-26-79
My variables are:
<xsl:for-each select="/root/outsideData">
<xsl:variable name="insideData">
<xsl:value-of select="." disable-output-escaping="yes"/>
</xsl:variable>
<xsl:variable name="Data">
<xsl:value-of
select="normalize-space(substring-after($insideData,'?>'))"
disable-output-escaping="yes"/>
</xsl:variable>
</xsl:foreach>
From the debug I can see that the variable insideData and Data are both value type of document-node(1)
Martin's solution works for me very well :)
But I'm still wondering why the following doesn't work:
<xsl:variable name="insideData">
<xsl:value-of select="." disable-output-escaping="yes"/>
</xsl:variable>
<ref>
<xsl:value-of select="substring-before(substring-after($insideData, '<nodeLevel22 ref'),>/>')"/>
</ref>
Here I got empty <ref/>
If you do <xsl:variable name="varName"><xsl:value-of select="..."/><xsl:variable> then you are creating a temporary document fragment that contains a single text with the string contents of the item(s) selected in the value-of. That does not make sense in most cases, doing <xsl:variable name="varName" select="..."/> is usually sufficient.
As for parsing the contents of the outsideData element with parse-xml, there is indeed not only the escaped XML document inside that element but white space as well, thus if you try to parse the contents as XML you get that error as white space before the XML declaration is not allowed. The whole approach of stuffing the XML into a CDATA section with an element with mixed contents is flawed in my view, if you want to store escaped XML into a CDATA then you should make sure that you use a single element that contains nothing but the CDATA section which then only contains the XML markup with no leading white space.
If you can't change the creation of the input data then you will need to make sure you pass in only that part of the string contents of the element to parse-xml that is a well-formed XML document, so you need some way to strip the white space before the XML declaration doing e.g.
<xsl:for-each select="/root/outsideData">
<xsl:variable name="xml-string" select="replace(., '^\s+', '')"/>
<xsl:variable name="xml-doc" select="parse-xml($xml-string)"/>
<!-- now output data e.g. -->
<xsl:value-of select="$xml-doc//nodeLevel1/nodeLevel22/#ref"/>
...
</xsl:for-each>
Untested but should show the right direction as far as trying to use parse-xml.

Resources