Is there way to fetch elements of previous iteration.
I have the following XML document (abbreviated for the better overview):
<requests>
<request>
<id>514</id>
<status>accepted</status>
<updated>"2013-10-07T12:00:51.508"</updated>
<query>
<![CDATA[Select column1 from table1]]>
</query>
</request>
<request>
<id>22</id>
<status>rejected</status>
<updated>"2012-11-07T12:00:51.508"</updated>
<query>
<![CDATA[Select column3 from table2]]>
</query>
</request>
<request>
<id>7523</id>
<status>accepted</status>
<updated>"2012-01-07T02:00:52.508"</updated>
<query>
<![CDATA[Select column8 from table3]]>
</query>
</request>
<request>
<id>84</id>
<status>accepted</status>
<updated>"2000-12-07T12:00:51.1"</updated>
<query>
<![CDATA[Select column1 from table1]]>
</query>
</request>
<request>
<id>999</id>
<status>accepted</status>
<updated>"2006-12-07T12:00:51.1"</updated>
<query>
<![CDATA[Select column1 from table1]]>
</query>
</request>
.
.
.
</requests>
Now I have to select all nodes with the status: "accepted" group them by the table and then by the column which is queried and for each column select only two requests with the most recent update time. The output should be ids of these node given as simple text. For example for the query 'Select column1 from table 1' 514 and 999 should be selected for output whereas 84 not. I have read about muenchian method but I could not apply it on the parsed text (in this case the text in query).
That is why I tried to figure out the way to obtain information from previous iteration so I can sort nodes by the given criteria and find id that I look for.
For example:
<xsl:for-each select="*[local-name()='requests']/*[local-name='request'][#status='accepted']" >
<xsl:sort select="string(*[local-name()='query']/text())" order="text" data-type="number" />
<xsl:sort select="#pdated" order="descending" data-type="number" />
<xsl:value-of select="string(preceding-sibling::*[1]/*[local-name()='query']/text()) />
Now this works but not the way I want it, it returns the preceding sibling in the document but not the query text of previous iteration. Is something like this possible?
Thanks
XSLT is a declarative language and while for-each might look like a procedural for loop there is no concept of that in the XSLT 1.0 or 2.0 specification. Therefore the concept of previous iteration does not exist either. Only XSLT 3.0 with the xsl:iterate provides something along those lines to allow processing of very large documents without loading the complete document tree into memory.
With XSLT 1.0 you will need to use a different approach, you would need to post the structure of the input samples you want to process and the corresponding result you want to create to allow us to help with concrete code.
I have read about muenchian method but I could not apply it on the
parsed text (in this case the text in query).
This is how you can apply Muenchian grouping based on parsed text - in this case, the column name:
<?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"/>
<xsl:strip-space elements="*"/>
<xsl:key name="req" match="request[status='accepted']" use="substring-before(substring-after(query ,'Select '), ' from')" />
<xsl:template match="/">
<root>
<xsl:for-each select="requests/request[status='accepted'][count(. | key('req', substring-before(substring-after(query ,'Select '), ' from'))[1]) = 1]">
<group>
<xsl:value-of select="query"/>
</group>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Applied to you example input, this will result in:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<group>Select column1 from table1</group>
<group>Select column8 from table3</group>
</root>
Now you just need to sort the group members by update time and output the first two.
Related
I have to transform my input xml using XSLT.
It contains, CDATA and I need to extract elements from CDATA and then I have to rename the tag.
Below is my input xml :
<getArtifactContentResponse>
<return>
<![CDATA[
<metadata>
<overview>
<name>scannapp</name>
<developerId>developer702</developerId>
<stateId>2</stateId>
<serverURL>dddd</serverURL>
<id>cspapp1103</id>
<description>scann doc</description>
<hostingTypeId>1</hostingTypeId>
</overview>
</metadata>
]]>
</return>
</getArtifactContentResponse>
And the expected output is :
<?xml version="1.0" encoding="UTF-8"?>
<metadata >
<information>
<name>scannapp</name>
<developerId>developer702</developerId>
<stateId>2</stateId>
<serverURL>ddddd</serverURL>
<id>cspapp1103</id>
<description>scann doc</description>
<hostingTypeId>1</hostingTypeId>
</Information>
</metadata>
XSLT I am using is below :
<xsl:output method="xml" version="1.0" encoding="UTF-8" />
<xsl:template match="/">
<xsl:value-of select="//ns:getArtifactContentResponse/ns:return/text()" disable-output-escaping="yes"/>
</xsl:template>
<xsl:template match="overview">
<Information>
<xsl:apply-templates select="#* | node()" />
</Information>
</xsl:template>
With this I am able to exrtact the CDATA but it is not renaming the element 'overview' to 'Information' .
Transformed xml is below :
<?xml version="1.0" encoding="UTF-8"?>
<metadata>
<overview>
<name>scannapp</name>
<developerId>developer702</developerId>
<stateId>2</stateId>
<serverURL>dddddd</serverURL>
<id>cspapp1103</id>
<description>scann doc</description>
<hostingTypeId>1</hostingTypeId>
</overview>
</metadata>
Can someone tell me how I can rename the tag after extracting the CDATA?
I don't understand what I am missing here?
Thanks in Advance
There are no elements in your CDATA, there is only text. That's what CDATA means: "this stuff might look like markup, but I want it treated as text".
Turning text into elements is called parsing, so to extract the elements from the text in your CDATA you are going to have to parse it. There's no direct way to do this in XSLT until you get to XSLT 3.0 (which has a parse-xml() function). Some XSLT processors have an extension function to do it; in some (I believe) the exslt:node-set() function does this if you supply a string as input. With others, you can call out to your own Java or Javascript code to do the parsing. So it all becomes processor-dependent.
Another approach is to output the XML in your CDATA section using the disable-output-escaping trick, and then process it in a second transformation.
The best approach is to get rid of the CDATA tags before you start. They should never have been put there in the first place.
<chapter>
<concept>
<title>*********************</title>
.
.
</concept>
<sections>
<title>*******************</title>
</chapter>
In the above structure I would like to retrieve text from <concept><title> or <sections><title>. i.e. using one xpath condition i need the value with below conditions.
1) if <concept><title> is not appeared then <sections><title>. vice verso also..
2) Both the title are available there then I want to consider nearst node value. i.e. in above structure "<sections><title>" is latest node.
You want the "nearest" (first) of the two:
(/*/*[self::concept or self::sections]/title)[1]
If you want the "latest" (last) of them, use:
(/*/*[self::concept or self::sections]/title)[last()]
XSLT - based verification:
<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:copy-of select=
"(/*/*[self::concept or self::sections]/title)[1]"/>
===============
<xsl:copy-of select=
"(/*/*[self::concept or self::sections]/title)[last()]"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<chapter>
<concept>
<title>*********Title 1************</title>
.
.
</concept>
<sections>
<title>**********Title 2*********</title>
</sections>
</chapter>
the two XPath expressions are evaluated and their results are copied to the output:
<title>*********Title 1************</title>
===============
<title>**********Title 2*********</title>
So in other words, you just want the last concept/title or sections/title that occurs under chapter? This XPath should do it (assuming the current context is chapter:
(concept/title | sections/title)[last()]
I have a XSL file in which I am creating a field like this:
<ServiceText>
<xsl:value-of select="concat(Yrs,'-',Mos,'-',Days,'-',Hrs)" />
</ServiceText>
The values of 'Yrs,'-',Mos,'-',Days,'-',Hrs , I am receiving from a Web service response and assiging it to the XSL directly. I cannot do any modification to the data in code for these fields, because that is how the ocde is. All data manipulation is on the xslt.
I want to do a data filtering on xslt as follows:
if value of yrs =-1 then yrs=""
if value of mos =-1 then mos=""
if value of Days =-1 then Days=""
if value of Hrs =-1 then Hrs=""
How can I do it on the XSL file?
XSLT 2.0:
<xsl:template match="/root">
<ServiceText>
<xsl:value-of select="string-join((Yrs, Mos, Days, Hrs)[.!=-1],'-')" />
</ServiceText>
</xsl:template>
See link for a Working example
In XSLT 1.0 use something like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="*/*[not(.= -1)]">
<xsl:value-of select="concat(., substring('-', 2 - not(self::Hrs)))"/>
</xsl:template>
<xsl:template match="*/*[. = -1]"/>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>
<Yrs>-1</Yrs>
<Mos>7</Mos>
<Days>15</Days>
<Hrs>3</Hrs>
</t>
the wanted result is produced:
7-15-3
Do Note:
It seems that there is an assumption that the "-1" values form a contiguous group (right to left in the sequence Yrs, Mos,Days).
If this assumption is violated, it would be impossible to understand what is the missing part in 2013-10-8 -- is it the months or the days ?
I'm going to be working the MS AX2010. When accessing data through an AX WCF service, the response is XML containing name / value pairs - known as a key data list. I'll be collecting this XML in BizTalk and needing to transform it to a canonical hierarchical schema. So for example, if I read a source Name element with "OrderNumber", then I would map the associated Value to an OrderNumber element in the destination schema.
Has anyone discovered a nice way to do this using a BizTalk map?
I acknowledge that you prefer to use the graphical functoids, but if you can accept an xslt route, it is pretty straightforward (See here for converting a visual map to an xslt). eg. the following XSLT
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="inputxmlns"
xmlns:ns1="outputxmlns"
exclude-result-prefixes="ns0"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/ns0:Root">
<ns1:Root>
<ns1:Elements>
<xsl:for-each select="ns0:Elements/ns0:Element">
<xsl:element name="ns1:{normalize-space(*[local-name()='Name']/text())}">
<xsl:value-of select="ns0:Value/text()"/>
</xsl:element>
</xsl:for-each>
</ns1:Elements>
</ns1:Root>
</xsl:template>
</xsl:stylesheet>
Will transform a quasi EAV schema:
<?xml version="1.0" encoding="utf-8"?>
<Root xmlns="inputxmlns">
<Elements>
<Element>
<Name>
NameOfElement1
</Name>
<Value>
ValueOfElement1
</Value>
</Element>
<Element>
<Name>
NameOfElement2
</Name>
<Value>
ValueOfElement2
</Value>
</Element>
</Elements>
</Root>
To this:
<?xml version="1.0" encoding="utf-8"?>
<ns1:Root xmlns:ns1="outputxmlns">
<ns1:Elements>
<ns1:NameOfElement1>
ValueOfElement1
</ns1:NameOfElement1>
<ns1:NameOfElement2>
ValueOfElement2
</ns1:NameOfElement2>
</ns1:Elements>
</ns1:Root>
I need to parse the following node:
<media:keywords>keyword1,keyword2<![CDATA[keyword3]]></media:keywords>
into a valid string, preferably "keyword1,keyword2,keyword3" but I would settle for removing the cdata completely.
Trying to access the node gives me the text "keyword1,keyword2keyword3" and I can't tell where the CDATA begins.
original xml (simplified version of mRSS feed)
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
<channel>
<item>
<media:keywords>keyword1,keyword2<![CDATA[keyword3]]></media:keywords>
</item>
</channel>
</rss>
xsl (simplified):
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:media="http://search.yahoo.com/mrss/" exclude-result-prefixes="xs xsi fn">
<xsl:output method="xml" encoding="UTF-8" omit-xml-declaration="yes"/>
<xsl:template match="/">
<test>
<xsl:variable name="items" select="/rss/channel/item"/>
<xsl:for-each select="$items">
<xsl:variable name="mediakw" select="media:keywords"/>
<xsl:element name="mediaKeyWords">
<xsl:value-of select="$mediakw"/>
</xsl:element>
</xsl:for-each>
</test>
</xsl:template>
</xsl:stylesheet>
and the output:
<test xmlns:media="http://search.yahoo.com/mrss/"><mediaKeyWords>keyword1,keyword2keyword3</mediaKeyWords></test>
Thanks a lot!
XML and XSLT cannot help you here.
XSLT uses the INFOSET model in which there isn't anything as a "CDATA node" and there is just a single text() node:
"keyword1,keyword2keyword3"
The XML document needs to be corrected and a comma be inserted between the substrings "keyword2" and "keyword3"
One solution would be to process the CDATA DOM node using DOM, and only then initiate the XSLT transformation.
By the time the XSLT processor sees the text, the CDATA is gone. You cannot see the incoming CDATA, and have very little control over how output CDATA is generated (all or nothing for a given tag).
Can't be done in standard XSLT.
The input XML you're receiving,
<media:keywords>keyword1,keyword2<![CDATA[keyword3]]></media:keywords>
is indistinguishable (to XSLT) from
<media:keywords>keyword1,keyword2keyword3</media:keywords>
because the CDATA markup is just a way of escaping the data inside it. There is really no special markup to escape in this case, so the CDATA happens to be a no-op. But XSLT has no way of knowing what data was originally expressed using CDATA, what was expressed using character entities, etc.
The solution would be to tell whoever is providing this XML that they need to put a delimiter between keyword2 and keyword3.