I am trying to find an xslt solution to the following problem I have.
I want to find a set of 3 subsequent rows that share the node name and an attribute but have a different values. The first row in the input contains an identifier, the second and third row contain values from a source system. I want to find the sets where the second and third row have different values.
E.g.
<eba7:mi235 contextRef="I-2014-E-dim-x43-x9-x156-x51-x14">78923</eba7:mi235>
<eba7:mi235 contextRef="I-2014-E-dim-x43-x9-x156-x51-x14">1111</eba7:mi235>
<eba7:mi235 contextRef="I-2014-E-dim-x43-x9-x156-x51-x14">2222</eba7:mi235>
There might also be sets of rows with only an identifier, a set of a row with an identifier and only one row with a value from the source system or a set of rows where the second and third row have the same value.
E.g.
<eba7:mi310 contextRef="I-2014-E-dim-x42-x9-x24-x195-x10-x4">78748</eba7:mi310>
<eba7:mi310 contextRef="I-2014-E-dim-x42-x9-x24-x195-x10-x4">0</eba7:mi310>
<eba7:mi310 contextRef="I-2014-E-dim-x42-x9-x25-x195-x10-x4">78804</eba7:mi310>
<eba7:mi310 contextRef="I-2014-E-dim-x42-x9-x25-x195-x10-x4">12345</eba7:mi310>
<eba7:mi310 contextRef="I-2014-E-dim-x42-x9-x25-x195-x10-x4">12345</eba7:mi310>
These I don't want to find in the output.
The output I want to create is
<eba7:mi235 id="78923" value1="1111" value2="2222" />
The structure of the input is such that the rows are always ordered like this. So I tried to access them using position, but that didn't work.
Could anybody point me in the right direction? Is using position the right way?
I have attached an file with the input data below
Thanks.
Paul.
<?xml version="1.0" encoding="utf-8"?>
<xbrl xml:lang="en" xmlns="http://www.xbrl.org/2003/instance" xmlns:eba7="http://www.eba.europa.eu/xbrl/crr/dict/met" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:link="http://www.xbrl.org/2003/linkbase">
<link:schemaRef xlink:type="simple" xlink:href="http://www.eba.europa.eu/eu/fr/xbrl/crr/fws/corep/its-2013-02/2014-07-31/mod/corep_con.xsd" />
<context id="I-2014-E">
<entity>
<identifier scheme="http://www.dnb.nl/id">578</identifier>
</entity>
<period>
<instant>2014-12-31</instant>
</period>
</context>
<eba7:mi310 contextRef="I-2014-E-dim-x42-x9-x24-x195-x10-x4">78748</eba7:mi310>
<eba7:mi310 contextRef="I-2014-E-dim-x42-x9-x24-x195-x10-x4">0</eba7:mi310>
<eba7:mi310 contextRef="I-2014-E-dim-x42-x9-x25-x195-x10-x4">78804</eba7:mi310>
<eba7:mi310 contextRef="I-2014-E-dim-x42-x9-x25-x195-x10-x4">12345</eba7:mi310>
<eba7:mi310 contextRef="I-2014-E-dim-x42-x9-x25-x195-x10-x4">12345</eba7:mi310>
<eba7:mi235 contextRef="I-2014-E-dim-x43-x9-x156-x51-x14">78923</eba7:mi235>
<eba7:mi235 contextRef="I-2014-E-dim-x43-x9-x156-x51-x14">1111</eba7:mi235>
<eba7:mi235 contextRef="I-2014-E-dim-x43-x9-x156-x51-x14">2222</eba7:mi235>
</xbrl>
I don't think the question is defined well enough; it can be interpreted in several ways.
If we assume that you want to:
Group all the given elements based on both the tag name and the #contextRef value being the same; with the mutual position of the elements being irrelevant for this purpose;
Count the distinct values in each group; if there are three or more, write an element with the common tag name to the output, and add a numbered attribute for each distinct value in this group;
then it would be probably best to do something like:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xbrli="http://www.xbrl.org/2003/instance"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:key name="k1" match="*" use="concat(name(), '|', #contextRef)"/>
<xsl:key name="k2" match="*" use="concat(name(), '|', #contextRef, '|', .)"/>
<xsl:template match="/xbrli:xbrl">
<xsl:copy>
<xsl:for-each select="*[count(.|key('k1', concat(name(), '|', #contextRef))[1])=1]">
<xsl:variable name="distinct-values" select="key('k1', concat(name(), '|', #contextRef)) [count(.|key('k2', concat(name(), '|', #contextRef, '|', .))[1])=1]"/>
<xsl:if test="count($distinct-values) >= 3">
<xsl:copy>
<xsl:for-each select="$distinct-values">
<xsl:attribute name="value{position()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:for-each>
</xsl:copy>
</xsl:if>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Applied to the following well-formed test input:
<xbrl xmlns="http://www.xbrl.org/2003/instance" xmlns:eba7="http://www.eba.europa.eu/xbrl/crr/dict/met">
<eba7:a contextRef="x">11</eba7:a>
<eba7:a contextRef="x">12</eba7:a>
<eba7:a contextRef="y">21</eba7:a>
<eba7:a contextRef="y">22</eba7:a>
<eba7:a contextRef="y">23</eba7:a>
<eba7:b contextRef="x">31</eba7:b>
<eba7:b contextRef="x">32</eba7:b>
<eba7:b contextRef="x">33</eba7:b>
<eba7:b contextRef="x">33</eba7:b>
<eba7:c contextRef="x">41</eba7:c>
<eba7:c contextRef="x">41</eba7:c>
<eba7:c contextRef="x">42</eba7:c>
<eba7:c contextRef="x">42</eba7:c>
</xbrl>
the result will be:
<?xml version="1.0" encoding="utf-8"?>
<xbrl xmlns="http://www.xbrl.org/2003/instance" xmlns:eba7="http://www.eba.europa.eu/xbrl/crr/dict/met">
<eba7:a value1="21" value2="22" value3="23"/>
<eba7:b value1="31" value2="32" value3="33"/>
</xbrl>
Note:
You must be familiar with the Muenchian grouping method in order to understand this;
Numbered attributes are not good XML practice. I would suggest you (or the powers that be) reconsider this requirement.
Would this stylesheet solve your problem:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xbrli="http://www.xbrl.org/2003/instance" version="1.0">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:key name="elements" match="*" use="#contextRef"/>
<xsl:template match="/xbrli:xbrl">
<xsl:copy>
<xsl:apply-templates select="*[#contextRef
and count(key('elements', #contextRef)) = 3
and key('elements', #contextRef)[2] != key('elements', #contextRef)[3]
and count(. | key('elements', #contextRef)[1]) = 1]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:attribute name="id">
<xsl:value-of select="."/>
</xsl:attribute>
<xsl:attribute name="value1">
<xsl:value-of select="key('elements', #contextRef)[2]"/>
</xsl:attribute>
<xsl:attribute name="value2">
<xsl:value-of select="key('elements', #contextRef)[3]"/>
</xsl:attribute>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Here, a key is declared to match elements with #contextRef being the identifier. The first template applies templates to the first elements with unique #contextRef(and also those which match other conditions like total elements with that #contextRef must be 3, and the second and thrid elements must not have the same value).
The next template matches these elements(from the first template), and creates the further output.
Related
I need to form an output xml in which I need to have the field value as a tag and then the subsequent next field value as value for the created tag.
<PrimaryKey>
<PK1FeildName>CONNO</PK1FeildName>
<PK1Value>001</PK1Value>
<PK2FeildName>INNO</PK2FeildName>
<PK2Value>123</PK2Value>
<PK3FeildName>CONNO</PK3FeildName>
<PK3Value>011</PK3Value>
</PrimaryKey>
Expected output:
<PrimaryKey>
<CONNO>001</CONNO>
<INNO>123</INNO>
<CONNO>011</CONNO>
</PrimaryKey>
If we consider that the elements will always appear in pairs where the value of the first element is the tag name and the second element is the value, then this works :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="PrimaryKey">
<xsl:element name="PrimaryKey">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="*">
<xsl:if test="count(preceding-sibling::*) mod 2 = 0">
<xsl:element name="{.}">
<xsl:value-of select="following-sibling::*[1]"/>
</xsl:element>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
You can try it here : https://xsltfiddle.liberty-development.net/bwdws3
Edit to answer comment
Example XML :
<Document>
<PrimaryKey>
<PK1FeildName>CONNO</PK1FeildName>
<PK1Value>001</PK1Value>
<PK2FeildName>INNO</PK2FeildName>
<PK2Value>123</PK2Value>
<PK3FeildName>CONNO</PK3FeildName>
<PK3Value>011</PK3Value>
</PrimaryKey>
<PrimaryKey>
<PK1FeildName>CONNO2</PK1FeildName>
<PK1Value>0012</PK1Value>
<PK2FeildName>INNO2</PK2FeildName>
<PK2Value>1232</PK2Value>
<PK3FeildName>CONNO2</PK3FeildName>
<PK3Value>0112</PK3Value>
</PrimaryKey>
</Document>
Modified XSLT :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:element name="PrimaryKey">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="PrimaryKey/*">
<xsl:if test="count(preceding-sibling::*) mod 2 = 0">
<xsl:element name="{.}">
<xsl:value-of select="following-sibling::*[1]"/>
</xsl:element>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bwdws3/1
For completeness, here's a solution that uses ESQL instead of XSLT:
CREATE FUNCTION Main() RETURNS BOOLEAN
BEGIN
-- Get a reference to the first instance of 'PK1FeildName'
DECLARE refPK REFERENCE TO InputRoot.XMLNSC.PrimaryKey.PK1FeildName[1];
WHILE LASTMOVE(refPK) DO
-- remember the field name
DECLARE keyName CHARACTER FIELDVALUE(refPK);
MOVE refPK NEXTSIBLING;
-- create the next field in the output
CREATE LASTCHILD OF OutputRoot.XMLNSC.Document.PrimaryKey TYPE NameValue NAME keyName VALUE FIELDVALUE(refPK);
MOVE refPK NEXTSIBLING;
END WHILE;
RETURN TRUE;
END;
Whether you use XSL or ESQL, I would recommend that you validate the incoming XML against an XSD because the mapping code depends on seeing specific tags in a specific order. You could add code to check the tag names, but XSD validation is a much easier solution.
Is it possible to sort nodes as follows:
Example XML
<record>
<id>0</id>
<sku>0</sku>
<name>Title</name>
<prop>456</prop>
<number>99</number>
</record>
If I apply this template
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="record/*">
<xsl:param select="." name="value"/>
<div>
<xsl:value-of select="concat(local-name(), ' - ', $value)"/>
</div>
</xsl:template>
</xsl:stylesheet>
Ouput:
<div>id - 0</div>
<div>sku - 0</div>
<div>name - Title</div>
<div>prop - 456</div>
<div>number - 99</div>
However, I would like all 0 values to be outputted last, as so:
<div>name - Title</div>
<div>prop - 456</div>
<div>number - 99</div>
<div>id - 0</div>
<div>sku - 0</div>
Is this possible by applying a sort to the <xsl:apply-templates/>?
There is an easy way of achieving this with XSLT-1.0. Just use a predicate on xsl:apply-templates checking if the content is zero:
<xsl:template match="record/*">
<div>
<xsl:value-of select="concat(local-name(), ' - ', .)"/>
</div>
</xsl:template>
<xsl:template match="/record">
<xsl:apply-templates select="*[normalize-space(.) != '0']" />
<xsl:apply-templates select="*[normalize-space(.) = '0']" />
</xsl:template>
This does not sort the output, but groups it the way you want it. The xsl:param is unnecessary.
As far as I see your question, the issue is not any sort at all.
You even didn't write what is the specific value, which you mentioned
it the title. It is rather an arbitrary sequence of child elements of record.
Try the following script, performing just that:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8" indent="yes" />
<xsl:template match="record">
<xsl:copy>
<xsl:apply-templates select="name, prop, number, id, sku"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record/*">
<div><xsl:value-of select="concat(local-name(), ' - ', .)"/></div>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()"/></xsl:copy>
</xsl:template>
</xsl:transform>
I used XSLT 2.0, because initially you did't specify the XSLT version.
Could you move on to version 2.0? As you can see, it allows to write quite
an elegent solution (impossible in version 1.0).
I also changed your template matching record/*. You actually don't
need any param. It is enough to use . - the value of the current
element.
Edit
Another possibility is that you want the following sort:
First, elements with non-numeric value (in your case, only name),
maybe without any sort.
Then elements with numeric value, sorted descending on this value.
If this is the case, then change the template matching record to the
following:
<xsl:template match="record">
<xsl:copy>
<xsl:apply-templates select="*[not(. castable as xs:integer)]"/>
<xsl:apply-templates select="*[. castable as xs:integer]">
<xsl:sort select="." order="descending" data-type="number"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
And add:
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
to transform tag.
But I still can't see anything, which can be called the specific value.
This is the input :
<Data>
<A_ID>123456789</A_ID>
<A_Code>ojhgf</A_Code>
<A_Rec>
<inner1>2345</inner1>
<inner2>14April</inner2>
<inner3>15November</inner3>
</A_Rec>
</Data>
This is my XSLT:
<xsl:variable name="AID" select="A_ID" />
<xsl:variable name="ACode" select="A_Code" />
<xsl:for-each xmlns:sch="http://schemas.w3.com/" select="//A_Rec">
<sch:COMPANY>
<xsl:value-of select="$AID" />
</sch:COMPANY>
<sch:COMPANY_CODE>
<xsl:value-of select="$ACode" />
</sch:COMPANY_CODE>
</xsl:for-each>
I am trying to get the value "123456789" in the below mentioned line. $AID should hold 123456789, i am getting the desired value outside the for-each loop though.
But I’m not getting AID and ACode values inside the for-each loop for Company and company code. What do I do?
You should be using "Data/A_ID". Assuming that your context node is not "Data" as I see nothing else wrong.
Once created, XSLT variables cannot change their value.
There's a very good SO answer here that may help you redesign your XSLT.
Edit: Lingamurthy CS has correctly identified an issue with your XPath, but take a look at the SO page I've linked as it still might be useful.
It seems you want to output A_ID and A_Code in a different node with a different namespace.
Try this instead:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="/">
<root xmlns:sch="http://schemas.w3.com/">
<xsl:apply-templates select="Data/A_ID|Data/A_Code"/>
</root>
</xsl:template>
<xsl:template match="Data/A_ID">
<xsl:element name="sch:COMPANY" namespace="http://schemas.w3.com/">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<xsl:template match="Data/A_Code">
<xsl:element name="sch:COMPANY_CODE" namespace="http://schemas.w3.com/">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Considering this XML,
XML:
<?xml version="1.0" encoding="UTF-8"?>
<items>
<book>
<title>doublebell</title>
<count>available</count>
</book>
<phone>
<brand>nokia</brand>
<model></model>
</phone>
</items>
Mapping Criteria while writing XSLT:
show the newbook/newtitle only if a value is present in input.
show the newbook/newcount only if a value is present in input.
show the newphone/newbrand only if a value is present in input.
show the newphone/newmodel only if a value is present in input.
XSLT:
<?xml version="1.0" encoding="ISO-8859-1"?>
<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" />
<xsl:variable name="book" select="items/book" />
<xsl:variable name="phone" select="items/phone" />
<xsl:template match="/">
<items>
<newbook>
<xsl:if test="$book/title!=''">
<newtitle>
<xsl:value-of select="$book/title" />
</newtitle>
</xsl:if>
<xsl:if test="$book/count!=''">
<newcount>
<xsl:value-of select="$book/count" />
</newcount>
</xsl:if>
</newbook>
<xsl:if test="$phone/brand!='' or $phone/model!=''"> <!-- not sure if this condition is required for the above mapping criteria -->
<newphone>
<xsl:if test="$phone/brand!=''">
<newbrand>
<xsl:value-of select="$phone/brand" />
</newbrand>
</xsl:if>
<xsl:if test="$phone/model!=''">
<newmodel>
<xsl:value-of select="$phone/model" />
</newmodel>
</xsl:if>
</newphone>
</xsl:if>
</items>
</xsl:template>
</xsl:stylesheet>
This is my concern:- In my actual XSLT, I have almost 70 conditions like
this, and everytime the XPath search is made twice [or thrice.. ] for
each condition [ for eg: <xsl:if test="$phone/brand!=''"> and <xsl:value-of select="$phone/brand" /> and outer if condition].
Is this much performance overhead? I don't feel it when I ran my application.
I like to hear from experienced people if this is correct way of writing the XSLT. Do I need to save the path in a variable and reuse it as done for $book
and $phone ? In such a case there will be 70+variables just to hold this.
You can approach this quite differently using templates. If you define a template that matches any element whose content is empty and does nothing:
<xsl:template match="*[. = '']" />
or possibly use normalize-space() if you want to consider elements to be empty if they contain only whitespace
<xsl:template match="*[not(normalize-space())]" />
Now with this in place add templates for the elements you are interested in
<xsl:template match="book">
<newbook><xsl:apply-templates /></newbook>
</xsl:template>
<xsl:template match="title">
<newtitle><xsl:apply-templates /></newtitle>
</xsl:template>
and so on. Now the book template will create a newbook element and go on to process its children. When it gets to the title it will have two different templates to choose from and will pick the "most specific" match. If the title is empty then the *[. = ''] template will win and nothing will be output, only if the title is non-empty will it create a newtitle element.
This way you let the template matcher do most of the work for you, you don't need any explicit conditional checks using xsl:if.
<?xml version="1.0" encoding="ISO-8859-1"?>
<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" />
<xsl:template match="/">
<items><xsl:apply-templates select="items/*" /></items>
</xsl:template>
<!-- ignore empty elements -->
<xsl:template match="*[not(normalize-space())]" />
<xsl:template match="book">
<newbook><xsl:apply-templates /></newbook>
</xsl:template>
<xsl:template match="title">
<newtitle><xsl:apply-templates /></newtitle>
</xsl:template>
<!-- and so on with similar templates for the other elements -->
</xsl:stylesheet>
Building on Ian's answer, you can also make a generic template that will create the "new" elements for you without having to specify each one individually. That would look like the below:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<items><xsl:apply-templates select="items/*" /></items>
</xsl:template>
<xsl:template match="*[not(normalize-space())]" />
<xsl:template match="*">
<xsl:element name="{concat('new',name())}">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
That last template just rebuilds the element by concatenating the word "new" to the front of it.
In that, I want to display only the unique fruit entries in it. Here is the XML tag what I'm using for parsing
<main>
<local id="1" type="Primary">
-<summary Date="23-02-12">
-<fruit>apple</fruit>
-<fruit>Orange</fruit>
</summary>
</local>
<local id="2" type="Primary">
-<summary Date="23-02-12">
-<fruit>apple</fruit>
-<fruit>mango</fruit>
</summary>
</local>
</main>
The expected result should be in the below format
<fruit>apple</fruit>
<fruit>Orange</fruit>
<fruit>Mango</fruit>
Here are the code snippet what I'm trying to use
<xsl:for-each select="main/local">
<xsl:for-each select="symbol/fruit">
<xsl:copy-of select="."/>
<xsl:copy-of select="fruit[not(.=$fruit)]"/>
</xsl:for-each>
</xsl:for-each>
But I'm not getting any output display for this, Can you please help me how can I remove this duplicate redundancy from here.? Thank You in advance
To do this in XSLT1.0 you can make use of a technique called 'Meunchian' grouping. First you define a key to 'look-up' the fruit elements based on the value
<xsl:key name="fruit" match="fruit" use="." />
Then, to get the unique fruit names, you match fruit elements that happen to be the first fruit element in the key (and to check two nodes are the same the generate-id() method is used)
<xsl:apply-templates
select="//fruit[generate-id() = generate-id(key('fruit', .)[1])]" />
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:key name="fruit" match="fruit" use="." />
<xsl:template match="/">
<xsl:apply-templates
select="//fruit[generate-id() = generate-id(key('fruit', .)[1])]" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output:
<fruit>apple</fruit>
<fruit>Orange</fruit>
<fruit>mango</fruit>