I have an XML like:
<ast>
<group>
<Set>
<location line="1" column="22" offset="22"/>
<group>
<Id value="foo">
<location line="1" column="31" offset="31"/>
</Id>
</group>
<group>
<Function>
<location line="1" column="22" offset="22"/>
<end-location line="1" column="49" offset="49"/>
<group>
<Id value="a">
<location line="1" column="35" offset="35"/>
</Id>
<Id value="b">
<location line="1" column="37" offset="37"/>
</Id>
</group>
<group>
<Return>
<location line="1" column="40" offset="40"/>
<Number value="0">
<location line="1" column="47" offset="47"/>
</Number>
</Return>
</group>
</Function>
</group>
</Set>
...
</group>
</ast>
which I process with this template:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="text()" /><!-- remove blanks -->
<xsl:template match="Set[group[position()=1]/Id][group[position()=2]/Function]">
<function-def>
<xsl:attribute name="name">
<xsl:value-of select="group[position()=1]/Id/#value" />
<xsl:text>(</xsl:text>
<xsl:apply-templates select="group[position()=2]/Function" />
<xsl:text>)</xsl:text>
</xsl:attribute>
<xsl:copy-of select="group/Function/location" />
<xsl:copy-of select="group/Function/end-location" />
</function-def>
</xsl:template>
<xsl:template match="Function/group[position()=1]/Id">
<xsl:value-of select="#value" />
<xsl:if test="position() != last()"><xsl:text>,</xsl:text></xsl:if>
</xsl:template>
</xsl:stylesheet>
however the condition position() != last() on the last template is not working. Why?
The output renders as:
<?xml version="1.0"?>
<function-def name="foo(a,b,)">...
while it should be:
<?xml version="1.0"?>
<function-def name="foo(a,b)">...
It is working, but not in the way you think....
In your first template, you have this xsl:apply-templates
<xsl:apply-templates select="group[position()=2]/Function" />
But you have no template matching Function, and so XSLT's built-in template rules kicks in, which is this...
<xsl:template match="*|/">
<xsl:apply-templates/>
</xsl:template>
This will select the Group elements, for which again there is no template. Now, when it does <xsl:apply-templates/> this will select all child nodes, which includes the empty text nodes used to indent the XML.
The problem is when you are testing position() = last() you are testing the position of the element in the set of all the child nodes that have been selected, which includes the text nodes. There is an empty text node after the last id, so id may be the last id element, but it is not the last child node.
One solution, is to tell XSLT to strip out empty text nodes, so that id then does become the last child node
<xsl:strip-space elements="*" />
Alternatively, you can add a template matching group, and explicitly select only id nodes
<xsl:template match="Function/group[position()=1]">
<xsl:apply-templates select="Id" />
</xsl:template>
Related
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>
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>.
What is the XSLT to get a parent node based on the value of the child?
My xml:
<cast>
<character>
<name>Bugs</name>
<id>1</id>
</character>
<character>
<name>Daffy</name>
<id>2</id>
</character>
I have tried this:
<xsl:template match="/cast/character/id">
<xsl:if test="text()=1">
<xsl:apply-templates select="../self" mode='copier'/>
</xsl:if>
</xsl:template>
<xsl:template match="*" mode='copier'>
<xsl:apply-templates />
</xsl:template>
But this outputs the text of every node in the document.
EDIT: I have to output XML here, I'm planning to extend this to XML generation
Just use:
<xsl:apply-templates select="/*/character[id=1]"/>
or, if the wanted node should just be copied with no further processing:
<xsl:copy-of select="/*/character[id=1]"/>
I believe you're printing everything because the processor starts matching at the root and the only template you specify is for id elements, so things get copied by default. Try this:
<stylesheet xmlns="http://www.w3.org/1999/XSL/Transform" version="1.0">
<output method="text" />
<template match="/">
<apply-templates select="//id" />
</template>
<template match="id">
<if test="text()='1'">
<value-of select=".." />
</if>
</template>
</stylesheet>
Or if you just want the character name, you could replace the select attribute value in the value-of element with "../name".
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/cast/character">
<xsl:if test="id=1">
<xsl:copy-of select="." />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Not able to add a comment. Hence put it here
I have the following schema:
<parent>
<child id="1" name="Child 1 Version 1" />
</parent>
<parent>
<child id="2" name="Child 2 Version 1" />
</parent>
<parent>
<child id="1" name="Child 1 Version 2" />
</parent>
I want to handle only the last node for each id. Below is what I have tried based on some reading:
<xsl:for-each select="//parent/child">
<xsl:sort select="#id"/>
<xsl:if test="not(#id=following-sibling::*/#id)">
<xsl:element name="child">
<xsl:value-of select="#name"/>
</xsl:element>
</xsl:if>
</xsl:for-each>
But it does not seem to work. My output still contains all three elements. Any ideas on what I can do to correct my issue?
That I want to only handle the last
node for each id. Below is what I have
tried based on some reading:
<xsl:for-each select="//parent/child">
<xsl:sort select="#id"/>
<xsl:if test="not(#id=following-sibling::*/#id)">
<xsl:element name="child">
<xsl:value-of select="#name"/>
</xsl:element>
</xsl:if>
</xsl:for-each>
But it does not seem to work. My
output still contains all three of the
elements. Any ideas on what I can do
to correct my issue?
The problem with this code is that even though the nodes are in a sorted node-set, their following-sibling s are still the ones in the document.
In order for this code to work, one would first create an entirely new document in which the nodes are sorted in the desired way, then (in XSLT 1.0 it is necessary to use the xxx:node-set() extension on the produced RTF to make it an ordinary XML document) on this document the nodes have their siblings as desired.
Solution:
This transformation presents one possible XSLT 1.0 solution that does not require the use of extension functions:
<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:key name="kchildById" match="child" use="#id"/>
<xsl:template match="/*">
<t>
<xsl:apply-templates select=
"*/child[generate-id()
=
generate-id(key('kchildById',
#id)[last()]
)
]
"/>
</t>
</xsl:template>
<xsl:template match="child">
<child>
<xsl:value-of select="#name"/>
</child>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML fragment (wrapped in a top element to become well-formed XML document and adding a second version for id="2"):
<t>
<parent>
<child id="1" name="Child 1 Version 1" />
</parent>
<parent>
<child id="2" name="Child 2 Version 1" />
</parent>
<parent>
<child id="1" name="Child 1 Version 2" />
</parent>
<parent>
<child id="2" name="Child 2 Version 2" />
</parent>
</t>
produces the wanted result:
<t>
<child>Child 1 Version 2</child>
<child>Child 2 Version 2</child>
</t>
Do note: the use of the Muenchian method for grouping.
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="kParentByChildId" match="parent" use="child/#id"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="parent[count(.|key('kParentByChildId',
child/#id)[last()]) != 1]"/>
</xsl:stylesheet>
Output:
<root>
<parent>
<child id="2" name="Child 2 Version 1"></child>
</parent>
<parent>
<child id="1" name="Child 1 Version 2"></child>
</parent>
</root>
Note. Grouping by #id, selecting last of the group.
Edit: Just in case this is confusing. Above stylesheet means: copy everything execpt those child not having the last #id of the same kind. So, it's not selecting the last of the group, but as reverse logic, striping not last in the group.
Second. Why yours is not working? Well, because of the following-sibling axis. Your method for finding the first of a kind is from an old time where there was few processor implementing keys. Now those days are gone.
So, this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="t">
<xsl:for-each select="parent/child">
<xsl:sort select="#id"/>
<xsl:if test="not(#id=following::child/#id)">
<xsl:element name="child">
<xsl:value-of select="#name"/>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Output:
<child>Child 1 Version 2</child>
<child>Child 2 Version 1</child>
Note: following axis, because child elements have not siblings.
I am looking to export from Filemaker using column names (instead of positions). Currently I export the following XSL stylesheet that exports by position with:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fm="http://www.filemaker.com/fmpxmlresult" exclude-result-prefixes="fm" >
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/">
<people>
<xsl:for-each select="fm:FMPXMLRESULT/fm:RESULTSET/fm:ROW">
<person>
<name>
<xsl:value-of select="fm:COL[01]/fm:DATA"/>
</name>
<location>
<xsl:value-of select="fm:COL[02]/fm:DATA"/>
</location>
</person>
</xsl:for-each>
</people>
</xsl:template>
</xsl:stylesheet>
Any ideas? Thanks.
If you just want to make the code more readable, then I'd suggest something simple, like:
<!-- expected columns -->
<xsl:variable name="NAME" value="1" />
<xsl:variable name="LOCATION" value="2" />
<!-- ... -->
<people>
<xsl:for-each select="fm:FMPXMLRESULT/fm:RESULTSET/fm:ROW">
<person>
<name>
<xsl:value-of select="fm:COL[$NAME]/fm:DATA"/>
</name>
<location>
<xsl:value-of select="fm:COL[$LOCATION]/fm:DATA"/>
</location>
</person>
</xsl:for-each>
</people>
BTW, with <xsl:value-of /> you can omit the fm:DATA, i.e. use:
<xsl:value-of select="fm:COL[$LOCATION] />
It will return the same result.
If you need something more sophisticated, please explain.
Update:
To refer to columns by column names is harder, but possible with something like that:
<!-- Define a key to get a field and all fields that precede it by the field name -->
<xsl:key name="N" match="/fm:FMPXMLRESULT/fm:METADATA/fm:FIELD" use="#NAME" />
<xsl:key name="N" match="/fm:FMPXMLRESULT/fm:METADATA/fm:FIELD"
use="following-sibling::fm:FIELD/#NAME" />
<!-- Then *count* them it in the code like that -->
<people>
<xsl:for-each select="fm:FMPXMLRESULT/fm:RESULTSET/fm:ROW">
<person>
<name>
<xsl:value-of select="fm:COL[count(key('N', 'name'))]" />
</name>
<location>
<xsl:value-of select="fm:COL[count(key('N', 'location'))]" />
</location>
</person>
</xsl:for-each>
</people>
Not utterly elegant, but works.