I would like to rename and move node one level up if node value is a certain value.
So far I wrote this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="stock/images/image[main_image = 1]">
<primary>
<xsl:apply-templates />
</primary>
</xsl:template>
</xsl:stylesheet>
Input:
<root>
<stock>
<code>Apple</code>
<images>
<image>
<img_file>1.jpg</img_file>
<main_image>1</main_image>
</image>
<image>
<img_file>2.jpg</img_file>
<main_image>0</main_image>
</image>
</images>
</stock>
</root>
Output:
<root>
<stock>
<code>Apple</code>
<images>
<primary>
<img_file>1.jpg</img_file>
<main_image>1</main_image>
</primary>
<image>
<img_file>2.jpg</img_file>
<main_image>0</main_image>
</image>
</images>
</stock>
</root>
It is almost good, but I would like to move <primary> node one level up, outside of <images>, like:
<root>
<stock>
<code>Apple</code>
<primary>
<img_file>1.jpg</img_file>
<main_image>1</main_image>
</primary>
<images>
<image>
<img_file>2.jpg</img_file>
<main_image>0</main_image>
</image>
</images>
</stock>
</root>
One way would be
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="stock[images[image[main_image = 1]]]">
<xsl:copy>
<xsl:apply-templates select="#* | *[not(self::images)]"/>
<xsl:apply-templates select="images/image[main_image = 1]"/>
<xsl:apply-templates select="images"/>
</xsl:copy>
</xsl:template>
<xsl:template match="images">
<xsl:copy>
<xsl:apply-templates select="#* | node()[not(self::image[main_image = 1])]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="stock/images/image[main_image = 1]">
<primary>
<xsl:apply-templates />
</primary>
</xsl:template>
</xsl:stylesheet>
How about:
XSLT 1.0
<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="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="stock">
<xsl:copy>
<xsl:apply-templates select="code"/>
<primary>
<xsl:apply-templates select="images/image[main_image = 1]/*"/>
</primary>
<images>
<xsl:apply-templates select="images/image[not(main_image = 1)]"/>
</images>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This is assuming a primary image always exists. Otherwise you would need to do:
XSLT 1.0
<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="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="stock">
<xsl:copy>
<xsl:apply-templates select="code"/>
<xsl:apply-templates select="images/image[main_image = 1]" mode="primary"/>
<images>
<xsl:apply-templates select="images/image[not(main_image = 1)]"/>
</images>
</xsl:copy>
</xsl:template>
<xsl:template match="image" mode="primary">
<primary>
<xsl:apply-templates/>
</primary>
</xsl:template>
</xsl:stylesheet>
Related
Is there a way for me to update the label element to Selma if "LastName" exists and if the label LastName doesn't exist then add the "LastName" and "label" elements to the XML?
<xml>
<udfs>
<udf>
<desc>FirstName</desc>
<label>Sam</label>
</udf>
<udf>
<desc>LastName</desc>
<label>Selman</label>
</udf>
</udfs>
</xml>
Here's what I have right now:
<xsl:stylesheet>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="udf[desc='LastName']/fieldValue">
<xsl:value-of select="'Selma'"/>
</xsl:template>
<xsl:template match="udf[not(desc='LastName')]">
<desc>LastName</desc>
<label>Selma</label>
</xsl:template>
</xsl:stylesheet>
I think you want to do:
XSLT 1.0
<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="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="udf[desc='LastName']/label">
<label>Selma</label>
</xsl:template>
<xsl:template match="udfs[not(udf/desc='LastName')]">
<xsl:copy>
<xsl:apply-templates/>
<udf>
<desc>LastName</desc>
<label>Selma</label>
</udf>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Input XML is
<Operations>
<ID>10</ID>
<UserArea>
<AdditionalPhantomInformation>
<PhantomItem>
<ItemCode>41341288</ItemCode>
<SubComponent>40241289</SubComponent>
<Position>1</Position>
</PhantomItem>
</AdditionalPhantomInformation>
</UserArea>
<ConsumedItem>
<LineNumber>3</LineNumber>
<ParentItem>40241288</ParentItem>
</ConsumedItem>
<UserArea>
<AdditionalPhantomInformation>
<PhantomItem>
<ItemCode>41341288</ItemCode>
<SubComponent>40241302</SubComponent>
<Position>5</Position>
</PhantomItem>
</AdditionalPhantomInformation>
</UserArea>
</Operations>
And my expected output is
<Operations>
<ID>10</ID>
<UserArea>
<AdditionalPhantomInformation>
<PhantomItem>
<ItemCode>41341288</ItemCode>
<SubComponent>40241289</SubComponent>
<Position>1</Position>
</PhantomItem>
<PhantomItem>
<ItemCode>41341288</ItemCode>
<SubComponent>40241302</SubComponent>
<Position>5</Position>
</PhantomItem>
</AdditionalPhantomInformation>
</UserArea>
<ConsumedItem>
<LineNumber>3</LineNumber>
<ParentItem>40241288</ParentItem>
</ConsumedItem>
</Operations>
I have searched various sources and tried, but i'm unable to get the xslt right. I don't know how to use xsl:for-each-group. Please help. I'm using XSLT 2.0.
You don't necessarily need xsl:for-each-group here, as all you are doing is combining specific nodes into one.
Start off with the identity template...
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
Or, if you could use XSLT 3.0....
<xsl:mode on-no-match="shallow-copy"/>
Then have a template matching the first UserArea which does the combining...
<xsl:template match="UserArea[1]">
<xsl:copy>
<xsl:apply-templates select="node()|following-sibling::UserArea/node()" />
</xsl:copy>
</xsl:template>
You would then just need another template to ensure the other UserArea elements are not output in their original position.
<xsl:template match="UserArea" />
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="UserArea[1]">
<xsl:copy>
<xsl:apply-templates select="node()|following-sibling::UserArea/node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="UserArea" />
</xsl:stylesheet>
Note, if you did want to use xsl:for-each-group you would do it this way
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Operations">
<xsl:copy>
<xsl:for-each-group select="*" group-by="name()">
<xsl:choose>
<xsl:when test="current-grouping-key() = 'UserArea'">
<xsl:copy>
<xsl:apply-templates select="current-group()/node()" />
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="current-group()" />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This has the advantage of being easily extended if you had nodes other than UserArea you wished to combine.
I have this XML:
<xml>
<row>
<image><![CDATA[javascript: open_window_zoom('http://example.com/image.php?image=/images/test/example.png&pID=46391&download=noid_90.png&name=Test name', 975, 366);]]></image>
<quantity>0</quantity>
</row>
<row>
<image><![CDATA[javascript: open_window_zoom('http://example.com/image.php?image=/images/test/another.png&pID=06395&download=anotherfile.png&name=Test name', 975, 366);]]></image>
<quantity>0</quantity>
</row>
</xml>
It is possible to extract pID=NUMBERHERE&download=FILENAMEHERE.png (and add new url before it)from <image> element?
The output should be like this:
<xml>
<row>
<image>http://newurl.com/pID=46391&download=noid_90.png</image>
<quantity>0</quantity>
</row>
<row>
<image>http://newurl.com/pID=06395&download=anotherfile.png</image>
<quantity>0</quantity>
</row>
</xml>
I tried some things, but I can't get the desidered result. For start I can copy the current structure:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This XSLT 2.0 stylesheet splits the value by & and then uses a predicate to filter only those that matches() the supplied regex where the parameter names are either pID or download. Using the #separator for xsl:value-of to join the values with &:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="image">
<xsl:copy>
<xsl:text>http://newurl.com/</xsl:text>
<xsl:value-of select="tokenize(., '&')[matches(., '(pID|download).*')]"
separator="&"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Alternatively, this XSLT 2.0 stylesheet uses xsl:analyze-string to extract the text that matches the provided regex:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="image">
<xsl:copy>
<xsl:text>http://newurl.com/</xsl:text>
<xsl:analyze-string select="." regex=".*(pID=.*&download=.*)&.*">
<xsl:matching-substring>
<xsl:value-of select="regex-group(1)"/>
</xsl:matching-substring>
<xsl:non-matching-substring></xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Try this XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="image">
<xsl:copy>
<xsl:value-of select="concat('http://newurl.com/pID=', substring-before(substring-after(text(), '&pID='), '&name='))"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
I'm trying to transform my input XML:
<?xml version="1.0" encoding="utf-8"?>
<request>
<customer_data>
<contact_info>
<name>john doe</name>
<dob_year>1984</dob_year>
<dob_month>09</dob_month>
<dob_date>14</dob_date>
<gender>m</gender>
</contact_info>
</customer_data>
</request>
so that it looks like this:
<?xml version="1.0" encoding="utf-8"?>
<request>
<customer_data>
<contact_info>
<name>john doe</name>
<dob>1984-09-14</dob>
<gender>m</gender>
</contact_info>
</customer_data>
</request>
Here is the XSLT, that I'm using:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="contact_info">
<xsl:copy>
<dob>
<xsl:value-of select="(dob_year, dob_month, dob_date)" separator="-"/>
</dob>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
And here is the result that I get:
<?xml version="1.0" encoding="utf-8"?><request>
<customer_data>
<contact_info><dob>1984-09-14</dob></contact_info>
</customer_data>
</request>
How do I transform the dob_* tags, keeping the rest of contact_info intact?
UPDATE
Based, on the answers below, my current solution is
<xsl:template match="contact_info">
<xsl:copy>
<xsl:apply-templates select="*[not(starts-with(name(), 'dob'))]"/>
<dob>
<xsl:value-of select="(dob_year, dob_month, dob_date)" separator="-"/>
</dob>
</xsl:copy>
</xsl:template>
This works for me, but isn't there more elegant way to express "apply transformation to given elements, and apply-templates to the rest"? Now, I'm kind of stuck with this expression *[not(starts-with(name(), 'dob'))] which is not that bad, but if the names of "DOB" attributes change, I'll have to fix this too.
Here's one way you could look at it:
<xsl:stylesheet version="2.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="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="dob_year">
<dob>
<xsl:value-of select="(., ../dob_month, ../dob_date)" separator="-"/>
</dob>
</xsl:template>
<xsl:template match="dob_month|dob_date"/>
</xsl:stylesheet>
Here's another:
<xsl:stylesheet version="2.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="*"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="contact_info">
<xsl:copy>
<xsl:copy-of select="name|gender"/>
<dob>
<xsl:value-of select="(dob_year, dob_month, dob_date)" separator="-"/>
</dob>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Edit:
In order to enumerate only the DOB elements and to do so only once, you could do:
<xsl:template match="contact_info">
<xsl:variable name="dob-fields" select="(dob_year, dob_month, dob_date)" />
<xsl:copy>
<xsl:apply-templates select="* except $dob-fields"/>
<dob>
<xsl:value-of select="$dob-fields" separator="-"/>
</dob>
</xsl:copy>
</xsl:template>
Change
<xsl:template match="contact_info">
<xsl:copy>
<dob>
<xsl:value-of select="(dob_year, dob_month, dob_date)" separator="-"/>
</dob>
</xsl:copy>
</xsl:template>
to
<xsl:template match="contact_info">
<xsl:copy>
<xsl:apply-templates select="dob_year/preceding-sibling::node()"/>
<dob>
<xsl:value-of select="(dob_year, dob_month, dob_date)" separator="-"/>
</dob>
<xsl:apply-templates select="dob_date/following-sibling::node()"/>
</xsl:copy>
</xsl:template>
I have a style-sheet that I am using to remove certain elements based on the value of an other element. However, it is not working ...
Sample Input XML
<Model>
<Year>1999</Year>
<Operation>ABC</Operation>
<Text>Testing</Text>
<Status>Ok</Status>
</Model>
If Operation value is 'ABC' then remove Text and Status nodes from XML.
And gives the following output.
<Model>
<Year>1999</Year>
<Operation>ABC</Operation>
</Model>
Here is my style sheet that I am using but it is removing Text and Status nodes from all XMLs even when operation is not 'ABC'.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="ID" select="//Operation"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Text | Status">
<xsl:if test ="$ID ='ABC'">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Thanks in Advance
How would I do the same when namespace is present like
<ns0:next type="Sale" xmlns:ns0="http://Test.Schemas.Inside_Sales">
Here is a complete XSLT transformation -- short and simple (no variables, no xsl:if, xsl:choose, xsl:when, xsl:otherwise):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"*[Operation='ABC']/Text | *[Operation='ABC']/Status"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<Model>
<Year>1999</Year>
<Operation>ABC</Operation>
<Text>Testing</Text>
<Status>Ok</Status>
</Model>
the wanted, correct result is produced:
<Model>
<Year>1999</Year>
<Operation>ABC</Operation>
</Model>
Change your xsl:if as follows:
<xsl:if test="../Operation!='ABC'">
and you can get rid of xsl:variable.
A better pattern in XSLT than using <xsl:if> is to add new templates with match conditions:
<xsl:template match="(Text | Status)[../Operation != 'ABC']"/>
I found this works:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/Model">
<xsl:choose>
<xsl:when test="Operation[text()!='ABC']">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="Year"/>
<xsl:apply-templates select="Operation"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>