I'm trying to convert an xml that has a root element and one level deep of child elements. These elements can have the same attribute name. What I am looking for is a way to transform the xml in such a way that the related attributes are nested correctly.
The xml is generated by an HTML form submission (I have control over the form field names).
The resulting XML is generated:
Input.xml
<root>
<project_id>1</project_id>
<project_name>Project 1</project_name>
<project_id>2</project_id>
<project_name>Project 2</project_name>
<project_id>3</project_id>
<project_name>Project 3</project_name>
</root>
Desired output
<root>
<project>
<id>1</id>
<name>Project 1</name>
</project>
<project>
<id>2</id>
<name>Project 2</name>
</project>
<project>
<id>3</id>
<name>Project 3</name>
</project>
<root>
My Attempt
Note: I prepended 'r_' to the repeated attributes. <r_project_id> 2</r_project_id>
<xsl:template match="/">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
<xsl:template match="node()|#*">
<project>
<xsl:apply-templates select="*[matches(name(), '^project_')]"/>
</project>
<project>
<xsl:apply-templates select="*[matches(name(), '^r_project')]"/>
</project>
</xsl:template>
<xsl:template match="*[matches(name(), '^r_project_')]">
<xsl:apply-templates select="*[matches(name(), '^r_project_')]"/>
<xsl:copy-of select="*"/>
</xsl:template>
<xsl:template match="*[matches(name(), '^project_')]">
<xsl:element name="{replace(name(), '^project_', '')}">
<xsl:copy-of select="*"/>
</xsl:element>
</xsl:template>
<xsl:template match="*[matches(name(), '^r_project_')]">
<xsl:element name="{replace(name(), '^r_project_', '')}">
<xsl:copy-of select="*"/>
</xsl:element>
</xsl:template>
Output.xml
<root>
<project>
<id></id>
<name></name>
</project>
<project>
<id></id>
<name></name>
<id></id>
<name></name>
</project>
</root>
Is there a simpler method to creating unique XML elements without having to create a extremely verbose xslt transformation that captures all possible repeated elements?
The solution from michael.hor257k looks fine, but a slightly more idiomatic and flexible solution in XSLT 2.0 might be
<xsl:template match="/">
<root>
<xsl:for-each-group select="root/*" group-starting-with="project_id">
<project>
<xsl:apply-templates select="current-group()" mode="rename"/>
</project>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template match="*" mode="rename">
<xsl:element name="{substring-after(name(), 'project_')}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
I can't follow the logic of your XSLT. Is there a reason why this couldn't be simply:
<xsl:template match="/">
<root>
<xsl:for-each select="root/project_id">
<project>
<id><xsl:value-of select="."/></id>
<name><xsl:value-of select="following-sibling::project_name"/></name>
</project>
</xsl:for-each>
</root>
</xsl:template>
</xsl:stylesheet>
Related
Given the following xml :
<tree>
<val>0</val>
<tree>
<val>1</val>
<tree>
<val>3</val>
</tree>
<tree>
<val>4</val>
</tree>
</tree>
<tree>
<val>2</val>
<tree>
<val>5</val>
</tree>
<tree>
<val>6</val>
</tree>
</tree>
</tree>
I need to transform it into this xml:
<root>0
<frst>1
<leaf>3</leaf>
<leaf>4</leaf>
</frst>
<second>2
<leaf>5</leaf>
<leaf>6</leaf>
</second>
</root>
This is my attempt that gives the same result, I recently started learning XSLT i'm not sure what other option I have, can this be improved or done in another way ?
Thank you for your help
This is my attempt :
<?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" encoding="UTF-8"/>
<xsl:template match="/tree">
<root>
<xsl:apply-templates />
</root>
</xsl:template>
<xsl:template match="val">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="tree">
<xsl:choose>
<!-- IF HAS CHILDREN -->
<xsl:when test="child::tree">
<xsl:if test="(count(preceding-sibling::tree)+1) = 1">
<frst>
<xsl:apply-templates/>
</frst>
</xsl:if>
<xsl:if test="(count(preceding-sibling::tree)+1) = 2">
<second>
<xsl:apply-templates/>
</second>
</xsl:if>
</xsl:when>
<!-- ELSE IS A LEAF -->
<xsl:otherwise>
<leaf>
<xsl:apply-templates/>
</leaf>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
test test
I would think the conditions can be written in match patterns:
<xsl:template match="tree[tree][1]">
<frst>
<xsl:apply-templates/>
</frst>
</xsl:template>
<xsl:template match="tree[tree][2]">
<second>
<xsl:apply-templates/>
</second>
</xsl:template>
<xsl:template match="tree[not(tree)]">
<leaf>
<xsl:apply-templates/>
</leaf>
</xsl:template>
although I guess your current code doesn't process the third/fourth/fifth.. tree children so an additional <xsl:template match="tree[tree][position() > 2]"/> might be necessary.
I am very new to XSL concepts and I am trying to create a XSLT for the below XML,
<?xml version="1.0" encoding="UTF-8"?>
<row>
<c1>1234</c1>
<c2>A</c2>
<c2 m="1" s="2">321</c2>
<c2 m="1" s="3">654</c2>
<c2 m="1" s="4">098</c2>
<c2 m="2">B</c2>
<c2 m="3">C</c2>
<c2 m="3" s="2">123</c2>
<c2 m="4">5</c2>
<c3 />
</row>
If transform using the XSL then I should get the output to be as follows,
1234 A\321\654\098]B]C\123]5
I have tried creating my own as below,
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="row">
<array />
<xsl:apply-templates />
</xsl:template>
<xsl:template match="c1">
<data>
<xsl:attribute name="attribute">1</xsl:attribute>
<xsl:attribute name="value">
<xsl:number level="single" />
</xsl:attribute>
<xsl:attribute name="subvalue">1</xsl:attribute>
<xsl:value-of select="." />
</data>
</xsl:template>
<xsl:template match="c2">
<data>
<xsl:attribute name="attribute">1</xsl:attribute>
<xsl:attribute name="value">
<xsl:number level="single" />
</xsl:attribute>
<xsl:attribute name="subvalue">1</xsl:attribute>
<xsl:value-of select="." />
</data>
</xsl:template>
</xsl:stylesheet>
but I get the output as follows,
1234 A 321 654 098 B C 123 5
Kindly help me out in creating the XSL
This is all very confusing. Your XSLT generates <array> and <data> elements, with various attributes, but there are no such elements or attributes in your desired output. In fact your sample code seems to bear absolutely no relationship to your desired output.
It's always hard to reverse-engineer requirements from a single example, but my attempt to do so would be this:
Output the string values of the children of the row element
If #m1 is greater than the previous #m1, precede the string value with "]"
If #m1 is equal to the previous #m1, precede it with "\"
If there is no #m1, precede it with a single space.
If my guesses are anywhere close to the mark, then the solution would look something like this:
<xsl:template match="row">
<xsl:apply-templates select="*"/>
</xsl:template>
<xsl:template match="row/*[#m1 > preceding-sibling::*[1]/#m1]">
<xsl:value-of select="concat(']', .)"/>
</xsl:template>
<xsl:template match="row/*[#m1 = preceding-sibling::*[1]/#m1]">
<xsl:value-of select="concat('\', .)"/>
</xsl:template>
<xsl:template match="row/*[not(#m1)]">
<xsl:value-of select="concat(' ', .)"/>
</xsl:template>
I am facing an xslt/xpath problem and hope someone could help, in a few words here is what I try to achieve.
I have to transform an XML document where some nodes may be missing, these missing nodes are mandatory in the final result. I have the set of mandatory node names available in an xsl:param.
The base document is:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="TRANSFORM.xslt"?>
<BEGIN>
<CLIENT>
<NUMBER>0021732561</NUMBER>
<NAME1>John</NAME1>
<NAME2>Connor</NAME2>
</CLIENT>
<PRODUCTS>
<PRODUCT_ID>12</PRODUCT_ID>
<DESCRIPTION>blah blah</DESCRIPTION>
</PRODUCTS>
<PRODUCTS>
<PRODUCT_ID>13</PRODUCT_ID>
<DESCRIPTION>description ...</DESCRIPTION>
</PRODUCTS>
<OPTIONS>
<OPTION_ID>1</OPTION_ID>
<DESCRIPTION>blah blah blah ...</DESCRIPTION>
</OPTIONS>
<PROMOTIONS>
<PROMOTION_ID>1</PROMOTION_ID>
<DESCRIPTION>blah blah blah ...</DESCRIPTION>
</PROMOTIONS>
</BEGIN>
Here is the stylesheet so far:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:param name="mandatoryNodes" as="xs:string*" select=" 'PRODUCTS', 'OPTIONS', 'PROMOTIONS' "/>
<xsl:template match="/">
<xsl:apply-templates select="child::node()"/>
</xsl:template>
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="BEGIN">
<xsl:element name="BEGIN">
<xsl:for-each select="$mandatoryNodes">
<!-- If there is no node with this name -->
<xsl:if test="count(*[name() = 'current()']) = 0">
<xsl:element name="{current()}" />
</xsl:if>
</xsl:for-each>
<xsl:apply-templates select="child::node()"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
I tried the transformation in XML Spy, the xsl:iftest failed saying that 'current item is PRODUCTS of type xs:string.
I've tried the same xsl:if outside of a for-each and it seems to work ... what am I missing ?
Inside of <xsl:for-each select="$mandatoryNodes"> the context item is a string but you want to access the primary input document and its nodes so you need to store that document or the template's context node in a variable and use that e.g.
<xsl:template match="BEGIN">
<xsl:variable name="this" select="."/>
<xsl:element name="BEGIN">
<xsl:for-each select="$mandatoryNodes">
<!-- If there is no child node of `BEGIN` with this name -->
<xsl:if test="count($this/*[name() = current()]) = 0">
<xsl:element name="{current()}" />
</xsl:if>
</xsl:for-each>
<xsl:apply-templates select="child::node()"/>
</xsl:element>
</xsl:template>
I have the following xml
<TopLevel>
<data m="R263">
<s ut="263firstrecord" lt="2013-02-16T09:21:40.393" />
<s ut="263secondrecord" lt="2013-02-16T09:21:40.393" />
</data>
<data m="R262">
<s ut="262firstrecord" lt="2013-02-16T09:21:40.393" />
<s ut="262secondrecord" lt="2013-02-16T09:21:40.393" />
</data>
</TopLevel>
I have some XSLT that does the call template but it's not itterating correctly.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="data">
<xsl:value-of select="#m" />
<xsl:variable name="vYourName" select="#m"/>
<xsl:choose>
<xsl:when test="#m='R262'">
<xsl:call-template name="R262"/>
</xsl:when>
</xsl:choose>
<xsl:choose>
<xsl:when test="#m='R263'">
<xsl:call-template name="R263"/>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template name="R262">
<xsl:for-each select="/TopLevel/data/s">
Column1=<xsl:value-of select="#ut" />
Column2=<xsl:value-of select="#lt" />
</xsl:for-each>
</xsl:template>
<xsl:template name="R263">
<xsl:for-each select="/TopLevel/data/s">
Column1=<xsl:value-of select="#ut" />
Column2=<xsl:value-of select="#lt" />
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
This gives me 8 records insead of the 4 (<s> level) records. I know it has to do with my iteration ... but I am not sure how to address this.
I am also aware of the apply stylesheets but I couldn't unravel that mystery either ... If someone can help me with XSLT that will only process everything from <TopLevel> to <\TopLevel> checking the value of m at the <data> level and applying the stylesheet at the <s> level for each <s> record I will be greateful beyond belief.
I don't know what output you want to produce, but I suspect you want to replace
<xsl:for-each select="/TopLevel/data/s">
by
<xsl:for-each select="s">
that is, you only want to process the "s" elements within the "data" you are currently processing, rather than selecting all the "s" elements in the whole document.
Why not do this using apply-templates?
<xsl:template match="data">
...
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="s[../#m='R262']">
...
</xsl:template>
<xsl:template match="s[../#m='R263']">
...
</xsl:template>
If you want to use match template and apply-templates you could do the following which gives you also a text output just like your stylesheet does. So this XSLT applied to your original source XML:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="data">
<xsl:value-of select="#m"/>
<xsl:apply-templates select="s"/>
</xsl:template>
<xsl:template match="s">
Column1=<xsl:value-of select="#ut"/>
Column2=<xsl:value-of select="#lt"/>
</xsl:template>
</xsl:stylesheet>
gives you this output:
<?xml version="1.0" encoding="UTF-8"?>
R263
Column1=263firstrecord
Column2=2013-02-16T09:21:40.393
Column1=263secondrecord
Column2=2013-02-16T09:21:40.393
R262
Column1=262firstrecord
Column2=2013-02-16T09:21:40.393
Column1=262secondrecord
Column2=2013-02-16T09:21:40.393
You basically only match on the s and give out the attributes "ut" and "lt". You can also output XML which would look better.
Using this XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
<xsl:template match="data">
<list>
<xsl:apply-templates select="s"/>
</list>
</xsl:template>
<xsl:template match="s">
<xsl:element name="record">
<xsl:attribute name="m">
<xsl:value-of select="parent::data/#m"/>
</xsl:attribute>
<item>Column1=<xsl:value-of select="#ut"/></item>
<item>Column2=<xsl:value-of select="#lt"/></item>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
will give you this nice XML output:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<list>
<record m="R263">
<item>Column1=263firstrecord</item>
<item>Column2=2013-02-16T09:21:40.393</item>
</record>
<record m="R263">
<item>Column1=263secondrecord</item>
<item>Column2=2013-02-16T09:21:40.393</item>
</record>
</list>
<list>
<record m="R262">
<item>Column1=262firstrecord</item>
<item>Column2=2013-02-16T09:21:40.393</item>
</record>
<record m="R262">
<item>Column1=262secondrecord</item>
<item>Column2=2013-02-16T09:21:40.393</item>
</record>
</list>
You have to adapt the original XSLT a little bit to get a nice XML structure. Also when matching s you "climb" up to element data to get the R-numbers for your attribute values.
The template matching root you need for a proper XML root element. <list> you could also get rid off then you have <record> as child of <root>.
This is in continuation of my previous questions (sorry for re-posting similar type of question again):
Merge functionality of two xsl files into a single file (not a xsl import or include issue)
and
Merge functionality of two xsl files into a single file (continued.....)
This is actually a bit manipulation of my second question.
I now need to merge the solution provided by Flack to my first question with a "choose" condition in my xsl:
<xsl:choose>
<xsl:when test='/Declaration/Header/DeclarantsReference = ""'>
<DeclarantsReference>
<xsl:text disable-output-escaping="no">A</xsl:text>
</DeclarantsReference>
</xsl:when>
<xsl:otherwise>
<DeclarantsReference>
<xsl:value-of select="/Declaration/Header/DeclarantsReference"/>
</DeclarantsReference>
</xsl:otherwise>
</xsl:choose>
Now any sample xml input like:
<Declaration>
<Message>
<Meduim>#+#</Meduim>
<CommonAccessReference></CommonAccessReference>
</Message>
<BeginingOfMessage>
<MessageCode>5</MessageCode>
<DeclarationCurrency></DeclarationCurrency>
<MessageFunction>ISD</MessageFunction>
</BeginingOfMessage>
<Header>
<DeclarantsReference></DeclarantsReference>
<Items>
<Documents>
<ItemDocument>
<DocumentCode>XXX</DocumentCode>
<DocumentPart></DocumentPart>
<DocumentLanguage>#+#</DocumentLanguage>
</ItemDocument>
</Documents>
</Items>
</Header>
</Declaration>
should output:
<Declaration>
<Message>
<Meduim></Meduim>
</Message>
<BeginingOfMessage>
<MessageCode>5</MessageCode>
<MessageFunction>ISD</MessageFunction>
</BeginingOfMessage>
<Header>
<DeclarantsReference>A</DeclarantsReference>
<Items>
<Documents>
<ItemDocument>
<DocumentCode>XXX</DocumentCode>
<DocumentLanguage></DocumentLanguage>
</ItemDocument>
</Documents>
</Items>
</Header>
</Declaration>
Thanks for any help in advance.
This stylesheet:
<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="*[not(node())]"/>
<xsl:template match="text()" name="strip">
<xsl:param name="pString" select="."/>
<xsl:param name="pOutput" select="substring-before($pString,'#+#')"/>
<xsl:choose>
<xsl:when test="contains($pString,'#+#')">
<xsl:call-template name="strip">
<xsl:with-param name="pString"
select="substring-after($pString,'#+#')"/>
<xsl:with-param name="pOutput"
select="concat($pOutput,
substring-before($pString,
'#+#'))"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat($pOutput,$pString)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="DeclarantsReference[not(node())]"
priority="1">
<xsl:copy>A</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Output:
<Declaration>
<Message>
<Meduim></Meduim>
</Message>
<BeginingOfMessage>
<MessageCode>5</MessageCode>
<MessageFunction>ISD</MessageFunction>
</BeginingOfMessage>
<Header>
<DeclarantsReference>A</DeclarantsReference>
<Items>
<Documents>
<ItemDocument>
<DocumentCode>XXX</DocumentCode>
<DocumentLanguage></DocumentLanguage>
</ItemDocument>
</Documents>
</Items>
</Header>
</Declaration>
Note: Rules overwriting the identity rule.