Multiple child elements in XSL for each - xslt

Am using for each tag in XSL:
Scenario
Input xml
<root>
<parent>
<Child 1>
<Child2>
data1
data2
data3
....
...
</child2>
<Child2>
data1
data2
data3
....
...
</child2>
<Child2>
data1
data2
data3
....
...
</child2>
</child1>
</parent>
When I am using for each on parent tag, in transformation file it is taking only the first occurence of child 2 repeatedly .
My requirement is, it need to take all the child two elements of child one.

Pretty sure from the comments that you've not got well-formed input XML. You can't have 2 R3 (root) elements at the top level of the document. This could be a push in the right direction.
Which data are you actually hoping to return, and in what format?
You've also hamstrung yourself by not having a repeating group inside Adress.
If you have the option, I'd reform your XML document into something more useful, because currently it's close to unworkable.
Recommend:
<?xml version="1.0" encoding="UTF-8"?>
<AddressBook owner="" date="">
<Address>
<addr firstName="" secondName="">
</addr>
<addr firstName="" secondName="">
</addr>
<addr firstName="" secondName="">
</addr>
</Address>
<Address>
<addr firstName="" secondName="">
</addr>
<addr firstName="" secondName="">
</addr>
<addr firstName="" secondName="">
</addr>
</Address>
</AddressBook>
This way, you can iterate through the elements which are named the same.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/AddressBook">
<xsl:for-each select="Address">
<xsl:for-each select="addr">
<xsl:value-of select="concat(#firstName, ', ', #secondName)"/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Alternately, if you need to add address lines, make a repeating group inside your addr elements, and give them id's to sort by, to make sure they're in order.
Hope this points you in the right direction.

Related

Can anyone explain how does the below xsl works?

Can anyone please exlain how the below xsl works with an example?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Remove empty elements or attributes -->
<xsl:template match="#*|node()">
<xsl:if test=". != '' or ./#* != ''">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When i use above xsl for below xml which is not indented (Note it is showing indented below but consider it not indented. The input text box did not allow me to put not indented xml):
<Book Edition="1234" Type="Novel" TimeStamp="2021-07-09T14:02:55-05:00" Version="1.003">
<BOS>
<LIB>
<RequestorID ID="XXX" Type="10"/>
</LIB>
</BOS>
<Sections>
<Section CreateDateTime="2021-07-03T11:21:43-05:00" CreatorID="XXX" Status="Read">
<UniqueID ID="443791" Type="10"/>
<Chapters>
<Chapter>
<Paragraphs>
<Paragraph NumberOfUnits="10" Lines="100">
<Rates>
<Rate EffectiveDate="2021-12-12" ExpireDate="2021-12-13" RateTimeUnit="Day" UnitMultiplier="1">
<Base AmountBeforeTax="145.90" CurrencyCode="USD"/>
</Rate>
</Rates>
</Paragraph>
</Paragraphs>
<Readers>
<Reader Age="10" Count="1"/>
</Readers>
<TimeSpan End="2021-12-13" Start="2021-12-12"/>
<BasicInfo BookCode="1310"/>
</Chapter>
</Chapters>
<Authors>
<Author AuthorRPH="1">
<Profiles>
<ProfileInfo>
<UniqueID ID="44379" Type="1"/>
<Profile ProfileType="1">
<Author>
<PersonName>
<GivenName>TEST</GivenName>
<Surname>TEST</Surname>
</PersonName>
<Telephone PhoneNumber="0"/>
<Email>test#test.com</Email>
<Address Type="H">
<AddressLine>123 MAIN ST</AddressLine>
</Address>
</Author>
</Profile>
</ProfileInfo>
</Profiles>
</Author>
</Authors>
<GlobalInfo>
<ReadIds>
<ReadId ReadID_Source="ZZZ" ReadID_Type="10" ReadID_Value="1234"/>
</ReadIds>
</GlobalInfo>
</Section>
</Sections>
</Book>
Then i get below output:
<Book Edition="1234" Type="Novel" TimeStamp="2021-07-09T14:02:55-05:00" Version="1.003">
<BOS>
<LIB>
<RequestorID ID="XXX" Type="10"/>
</LIB>
</BOS>
<Sections>
<Section CreateDateTime="2021-07-03T11:21:43-05:00" CreatorID="XXX" Status="Read">
<UniqueID ID="443791" Type="10"/>
<Authors>
<Author AuthorRPH="1">
<Profiles>
<ProfileInfo>
<UniqueID ID="44379" Type="1"/>
<Profile ProfileType="1">
<Author>
<PersonName>
<GivenName>TEST</GivenName>
<Surname>TEST</Surname>
</PersonName>
<Telephone PhoneNumber="0"/>
<Email>test#test.com</Email>
<Address Type="H">
<AddressLine>123 MAIN ST</AddressLine>
</Address>
</Author>
</Profile>
</ProfileInfo>
</Profiles>
</Author>
</Authors>
</Section>
</Sections>
</Book>
As seen above it removes everything inside the Chapters tag and Global info tag
But if i use above xsl for below xml which indented:
<Book Edition="1234" Type="Novel" TimeStamp="2021-07-09T14:02:55-05:00" Version="1.003">
<POS>
<Source>
<RequestorID ID="XXX" Type="10"/>
</Source>
</POS>
<Sections>
<Section CreateDateTime="2021-07-03T11:21:43-05:00" CreatorID="XXX" Status="Read">
<UniqueID ID="443791" Type="10"/>
<Chapters>
<Chapter>
<Paragraphs>
<Paragraph NumberOfUnits="10" Lines="100">
<Rates>
<Rate EffectiveDate="2021-12-12" ExpireDate="2021-12-13" RateTimeUnit="Day" UnitMultiplier="1">
<Base AmountBeforeTax="145.90" CurrencyCode="USD"/>
</Rate>
</Rates>
</Paragraph>
</Paragraphs>
<Readers>
<Reader Age="10" Count="1"/>
</Readers>
<TimeSpan End="2021-12-13" Start="2021-12-12"/>
<BasicInfo BookCode="1310"/>
</Chapter>
</Chapters>
<Authors>
<Author AuthorRPH="1">
<Profiles>
<ProfileInfo>
<UniqueID ID="44379" Type="1"/>
<Profile ProfileType="1">
<Author>
<PersonName>
<GivenName>TEST</GivenName>
<Surname>TEST</Surname>
</PersonName>
<Telephone PhoneNumber="0"/>
<Email>test#test.com</Email>
<Address Type="H">
<AddressLine>123 MAIN ST</AddressLine>
</Address>
</Author>
</Profile>
</ProfileInfo>
</Profiles>
</Author>
</Authors>
<GlobalInfo>
<ReadIds>
<ReadId ReadID_Source="ZZZ" ReadID_Type="10" ReadID_Value="1234"/>
</ReadIds>
</GlobalInfo>
</Section>
</Sections>
</Book>
Then i get correct output:
<Book Edition="1234" Type="Novel" TimeStamp="2021-07-09T14:02:55-05:00" Version="1.003">
<BOS>
<LIB>
<RequestorID ID="XXX" Type="10"/>
</LIB>
</BOS>
<Sections>
<Section CreateDateTime="2021-07-03T11:21:43-05:00" CreatorID="XXX" Status="Read">
<UniqueID ID="443791" Type="10"/>
<Chapters>
<Chapter>
<Paragraphs>
<Paragraph NumberOfUnits="10" Lines="100">
<Rates>
<Rate EffectiveDate="2021-12-12" ExpireDate="2021-12-13" RateTimeUnit="Day" UnitMultiplier="1">
<Base AmountBeforeTax="145.90" CurrencyCode="USD"/>
</Rate>
</Rates>
</Paragraph>
</Paragraphs>
<Readers>
<Reader Age="10" Count="1"/>
</Readers>
<TimeSpan End="2021-12-13" Start="2021-12-12"/>
<BasicInfo BookCode="1310"/>
</Chapter>
</Chapters>
<Authors>
<Author AuthorRPH="1">
<Profiles>
<ProfileInfo>
<UniqueID ID="44379" Type="1"/>
<Profile ProfileType="1">
<Author>
<PersonName>
<GivenName>TEST</GivenName>
<Surname>TEST</Surname>
</PersonName>
<Telephone PhoneNumber="0"/>
<Email>test#test.com</Email>
<Address Type="H">
<AddressLine>123 MAIN ST</AddressLine>
</Address>
</Author>
</Profile>
</ProfileInfo>
</Profiles>
</Author>
</Authors>
<GlobalInfo>
<ReadIds>
<ReadId ReadID_Source="ZZZ" ReadID_Type="10"
ReadID_Value="1234"/>
</ReadIds>
</GlobalInfo>
</Section>
</Sections>
</Book>
Can anyone explain if the xmls are same why does indentation give different output? Does proper indentation affect the xsl transformaion?
The code has a single template rule which matches all element, text, comment, processing instruction, and attribute nodes. If the node has a non-empty string value, or has an attribute with a non-empty string value, then it shallow-copies the node and processes its attributes and children recursively.
The overall effect is to copy the entire document except for elements that have no content and no non-empty attributes (such as <br/>) - plus a few other exceptions such as empty comments.
The XPath expression . != '' or ./#* != '' in the test might not behave as you expect. The specification for XPath 1.0 (usually used with XSLT 1.0), as indicated by version="1.0" says at https://www.w3.org/TR/1999/REC-xpath-19991116/#dt-string-value
For every type of node, there is a way of determining a string-value
for a node of that type. For some types of node, the string-value is
part of the node; for other types of node, the string-value is
computed from the string-value of descendant nodes.
and down in section "5.2 Element Nodes"
The string-value of an element node is the concatenation of the
string-values of all text node descendants of the element node
in document order.
So for an ordinary non-indented source element, string conversion involves recursively descending to elements and getting their values (ignoring attributes). In your case, for an element which also does not have attributes, the whole sub-tree gets eliminated (i.e. not copied to output) if it does not have any element with a value like <elem>value</elem> in it.
With indented source, you also have nodes representing the whitespace between the nodes. (XPath does not know that white-space is irrelevant for you, and assumes mixed content.) This causes to-string conversion of an element with (indented) sub-elements to have (at least) that whitespace in the result of the string conversion, rendering the . != '' XPath expression false.
I hope, this explains the difference in result depending on indentation in source.
You might want to have a look at https://www.w3.org/TR/1999/REC-xpath-19991116/#function-normalize-space for trimming the conversion result. Note that this would potentially also affect honoring of "real values" if these can be all whitespace.
Edit: Depending on what you want to achieve, you might consider having white-space eliminated by the XSLT processor by using xsl:strip-space.

How to find unmatched rows with XSLT

I have two large xml files, one of which has the following format:
<Persons>
<Person>
<ID>1</ID>
<LAST_NAME>London</LAST_NAME>
</Person>
<Person>
<ID>2</ID>
<LAST_NAME>Twain</LAST_NAME>
</Person>
<Person>
<ID>3</ID>
<LAST_NAME>Dikkens</LAST_NAME>
</Person>
</Persons>
The second file has the following format:
<SalesPersons>
<SalesPerson>
<ID>2</ID>
<LAST_NAME>London</LAST_NAME>
</SalesPerson>
<SalesPerson>
<ID>3</ID>
<LAST_NAME>Dikkens</LAST_NAME>
</SalesPerson>
</SalesPersons>
I need to find those records from file 1, which does not exist in file 2. Although I have it done using for-each loop, such an approach is taking a substantial amount of time. Is it possible to somehow make it run faster using a different approach?
Using a key can help to improve performance on lookups:
<xsl:key name="sales-person" match="SalesPerson" use="concat(ID, '|', LAST_NAME)"/>
<xsl:template match="/">
<xsl:for-each select="Persons/Person">
<xsl:variable name="person" select="."/>
<!-- need to change context document for key function use -->
<xsl:for-each select="$doc2">
<xsl:if test="not(key('sales-person', concat($person/ID, '|', $person/LAST_NAME)))">
<xsl:copy-of select="$person"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
That assumes you have bound doc2 as a variable or parameter with e.g. <xsl:param name="doc2" select="document('sales-persons.xml')"/>.

XSL for-each filter with subcontext from other main-node

i've already a new problem.
I have to merge lists via xsl, problem on this is that the key in the lookup-list must be concatinated by 2 values.
The lists can be relative large with thousands,ten-thousands in some cases even more of entries in both lists. In advance of large sizes of this lists, i have to look on performance and memory. It could be that this will later implemented in an web-service-client, so it must run quick and resource-saving.
Merging the exisiting Elements in List1 and List2 is done and was not complicated, but now i have to check both lists on non-exisiting elements in other list.
I tried to negate the for-each select statement but failed and it is presumably the wrong way.
InputXML-example
<ROOT>
<getObjectListResponse>
<item>
<Key>1111111:aaaa</Key>
<someOhterData>Text</someOhterData>
</item>
<item>
<Key>2222222:bbbb</Key>
<someOhterData>Text</someOhterData>
</item>
<item>
<Key>3333333:aaaa</Key>
<someOhterData>Text</someOhterData>
</item>
</getObjectListResponse>
<LookupList>
<DATA>
<KeyPart1>1111111</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>1111111</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>2222222</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>2222222</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>3333333</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>3333333</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
</LookupList>
</ROOT>
The first part, find the existing parts in both lists is already done.
The second part is to find non-existing parts in List 1 to List 2 and List 2 to List 1.
I wanna like to do this in for-each, so you get only non-exisiting entries from List1 which does not exists in List2.
My Problem ist to lookup in for-each context with an concatinated key, from all DATA in LookupList.
<xsl:for-each select="/*/getObjectListResponse/item[Key/text() != /*/LookupList/DATA/*[concat(KeyPart1,'/',KeyPart2)]]">
<xsl:copy-of select="."/>
</xsl:for-each>
<xsl:for-each select="/*/getObjectListResponse/item[Key/text() != /*/LookupList/DATA/[concat(KeyPart1,'/',KeyPart2)]]">
<xsl:copy-of select="."/>
</xsl:for-each>
But everything i tried fails, with no results or wrong result.
How can this be done?
I tried this and some others, but nothing will work.
Thanks in advance
I would use keys for the cross-reference, here is an XSLT 3.0 (as supported by Saxon 9.8 all editions or Altova XMLSpy/Raptor) stylesheet as obviously one sample is a good use case for a composite key:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
exclude-result-prefixes="xs math"
version="3.0">
<xsl:output indent="yes"/>
<xsl:key name="data" match="DATA" composite="true" use="KeyPart1, KeyPart2"/>
<xsl:key name="item" match="item" use="Key"/>
<xsl:template match="ROOT">
<xsl:copy>
<items-not-in-data>
<xsl:copy-of select="getObjectListResponse/item[not(key('data', (substring-before(Key, ':'), substring-after(Key, ':'))))]"/>
</items-not-in-data>
<data-not-in-items>
<xsl:copy-of select="LookupList/DATA[not(key('item', concat(KeyPart1, ':', KeyPart2)))]"/>
</data-not-in-items>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
For your sample data I get
<ROOT>
<items-not-in-data/>
<data-not-in-items>
<DATA>
<KeyPart1>1111111</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>2222222</KeyPart1>
<KeyPart2>aaaa</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
<DATA>
<KeyPart1>3333333</KeyPart1>
<KeyPart2>bbbb</KeyPart2>
<someOhterData>Text</someOhterData>
</DATA>
</data-not-in-items>
</ROOT>
Of course XSLT 3.0 and a composite key is not mandatory, you could as well use XSLT 2.0 and use a single key value concat(KeyPart1, KeyPart2).

In XSLT, many to many attributes comparing in for-each condition

Using XSLT 1.0
Is it possible to filter many to many attributes, i mean as below example:
"../../../../fieldmap/field[#name" i.e. more then 1 elements as fieldmap containing "field/#name" attribute are exists and it is comparing with definition/#title and there too more then one definition element exists containing #title.
EXAMPLE:
<xsl:for-each select="../../../../fieldmaps/field[#name=../destination/#title]">
Can you please suggest me how it could possible to achieve -- if field containing #name existed in any of defination/#title then only those records should be process within for-each loop?
(as now, it looks, it will just compare with first #title attribute and consider all fieldmaps/field/#name attributes)
Thanks
You can achieve that using a variable:
<xsl:variable name="titles" select="../destination/#title"/>
<!--now "titles" contains a nodeset with all the titles -->
<xsl:for-each select="../../../../fieldmaps/field[#name=$titles]">
<!-- you process each field with a name contained inside the titles nodeset -->
</xsl:for-each>
Here you have a simplified example:
INPUT:
<parent>
<fieldmaps>
<field name="One"/>
<field name="Two"/>
<field name="Three"/>
</fieldmaps>
<destinations>
<destination title="One"/>
<destination title="Two"/>
</destinations>
</parent>
TEMPLATE:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- ++++++++++++++++++++++++++++++++ -->
<xsl:template match="parent">
<Results>
<xsl:variable name="titles" select="destinations/destination/#title"/>
<xsl:for-each select="fieldmaps/field[#name=$titles]">
<Result title="{#name}"/>
</xsl:for-each>
</Results>
</xsl:template>
<!-- ++++++++++++++++++++++++++++++++ -->
</xsl:stylesheet>
OUTPUT:
<Results>
<Result title="One"/>
<Result title="Two"/>
</Results>
I hope this helps!

Help with XSLT Transformation

I was hoping I could get a little assistance with an XSLT transform. I can't seem to get it right.
Here is a sample of the source xml document:
<?xml version="1.0" encoding="UTF-8"?>
<Locations>
<header>
<location>Location Field</location>
<job_function>Job Function Field</job_function>
<count>Count</count>
</header>
<data>
<location>2177</location>
<job_function>ADM</job_function>
<count>1</count>
</data>
<data>
<location>2177</location>
<job_function>OPS</job_function>
<count>1</count>
</data>
<data>
<location>2177</location>
<job_function>SLS</job_function>
<count>5</count>
</data>
<data>
<location>2179</location>
<job_function>ADM</job_function>
<count>1</count>
</data>
<data>
<location>2179</location>
<job_function>SEC</job_function>
<count>1</count>
</data>
</Locations>
I want to transform it into the following format:
<Locations>
<data>
<PeopleSoftID>2177</PeopleSoftID>
<ADM>1</ADM>
<OPS>1</OPS>
<SLS>5</SLS>
<TotalCount>7</TotalCount>
</data>
<data>
<PeopleSoftID>2179</PeopleSoftID>
<ADM>1</ADM>
<SEC>1</SEC>
<TotalCount>2</TotalCount>
</data>
</Locations>
So basically, as you can see in the sample source document there are multiple elements that have the same value. In the destination document, there should now only be one record (<PeopleSoftID> element) per <location> element value in the source document. Since there were 3 <location> elements with the value of 2177, the destination document now has just 1 <PeopleSoftID> element that contains that value. The value of the <job_function> element in the source document becomes an element in the destination document. The value of that new element ends up being the sibling value of the <count> element from the source document. The <TotalCount> element in the destination document is the SUM of the values of all the new elements that are generated from the source <job_function> element.
I hope that explanation did not confuse anybody =).
I am a little new to XSLTs still so I am having trouble getting the logic right on this.
I can only use XSLT 1.0 too.
If I did not provide enough information let me know, and I will try to provide more as soon as I am able.
Thanks guys!
Read up on xsl:key and grouping with the Muenchian Method
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" />
<!--Group the data elements by their location values -->
<xsl:key name="data-by-location" match="data" use="location" />
<xsl:template match="Locations">
<xsl:copy>
<!--Get a distinct list of location values,
using the Muenchian Method -->
<xsl:for-each
select="data[generate-id() =
generate-id(key('data-by-location', location)[1])]">
<xsl:copy>
<PeopleSoftID>
<xsl:value-of select="location"/>
</PeopleSoftID>
<!--For every data element matching this location... -->
<xsl:for-each select="key('data-by-location',location)">
<!--Create an element using the job_function
as the element name -->
<xsl:element name="{job_function}">
<!--The value of the count element
as the value of the generated element-->
<xsl:value-of select="count"/>
</xsl:element>
</xsl:for-each>
<TotalCount>
<!--calculate the sum of all the count element values
for this location -->
<xsl:value-of select="sum(key('data-by-location',
location)/count)"/>
</TotalCount>
</xsl:copy>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>