I am trying to use a key to group <p> elements based on whether they have a specific processing-instruction as a first preceding sibling, but I am having no luck.
For the following example input, I would like each <p> that has a first preceding-sibling processing-instruction that contains “key” to be grouped with its sibling <p> elements that meet the same criteria. Example input:
<root>
<p>not in key</p>
<?samplePI key?>
<p>start of key; </p>
<?samplePI key?>
<p>in key 1; </p>
<?samplePI key?>
<p>in key 2; </p>
<p>Not in key</p>
<?samplePI key?>
<p>start of new key; </p>
<?samplePI key?>
<p>in new key 3;</p>
</root>
Example output:
<root>
<p>not in key</p>
<p>start of key; in key 1; in key 2;</p>
<p>Not in key</p>
<p>start of new key; in new key 3;</p>
</root>
An example of what I've got:
<xsl:template match="root">
<root>
<xsl:apply-templates/>
</root>
</xsl:template>
<xsl:template match="p">
<xsl:choose>
<xsl:when test="preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]][preceding-sibling::p[1][(preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]])]]">
</xsl:when>
<xsl:when test="preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]][preceding-sibling::p[1][not(preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]])]]">
<p><xsl:value-of select="text()"/>
<xsl:apply-templates select="key('nodes', generate-id())" mode="groupedParas"/>
</p>
</xsl:when>
<xsl:otherwise>
<p><xsl:apply-templates/></p>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="p" mode="groupedParas">
<xsl:apply-templates/>
</xsl:template>
<xsl:key name="nodes" match="node()[(self::p[preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]]])]" use="generate-id((preceding-sibling::p)[last()])"/>
<xsl:template match="text()">
<xsl:value-of select="."/>
</xsl:template>
Note that I need the help with getting the correct key syntax, as opposed to generating the desired structure. I need to use XSLT 1.0 for this. Any help appreciated.
With XSLT 2.0 it looks manageable
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="2.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="root">
<xsl:copy>
<xsl:for-each-group select="*" group-adjacent="boolean(preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]])">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<p>
<xsl:apply-templates select="current-group()/node()"/>
</p>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With XSLT 1.0 my usual approach is sibling recursion but it needs nastily long and convoluted match patterns:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="processing-instruction()[contains(., 'key')]"/>
<xsl:template match="p[preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]]
and preceding-sibling::node()[2][not(self::p[preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]]])]]">
<p>
<xsl:apply-templates select="." mode="collect"/>
</p>
</xsl:template>
<xsl:template match="p[preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]]
and preceding-sibling::node()[2][self::p[preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]]]]]"/>
<xsl:template match="p" mode="collect">
<xsl:apply-templates/>
<xsl:apply-templates select="following-sibling::node()[2][self::p and preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]]]" mode="collect"/>
</xsl:template>
</xsl:stylesheet>
And finally, as you seem to want to use a key, a variation of the sibling recursion shown above which uses a key to identity a group of p elements, is as follows:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="1.0">
<xsl:strip-space elements="*"/>
<xsl:output indent="yes"/>
<xsl:key name="collect"
match="p[preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]]
and preceding-sibling::node()[2][self::p[preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]]]]]"
use="generate-id(preceding-sibling::p[preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]]
and not(preceding-sibling::node()[2][self::p[preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]]]])][1])"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="processing-instruction()[contains(., 'key')]"/>
<xsl:template match="p[preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]]
and preceding-sibling::node()[2][not(self::p[preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]]])]]">
<p>
<xsl:apply-templates select="./node() | key('collect', generate-id())/node()"/>
</p>
</xsl:template>
<xsl:template match="p[preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]]
and preceding-sibling::node()[2][self::p[preceding-sibling::node()[1][self::processing-instruction()[contains(., 'key')]]]]]"/>
</xsl:stylesheet>
Related
I am XSLT beginner, learning by example and by working on projects. Currently, I am working on creating grouped, nested structure from flat.
Consider this sample xml input:
<root>
<a>First text</a>
<b>Text</b>
<c>More text in c tag</c>
<d>There is even d tag</d>
<a>Another "a" test.</a>
<b>ěščřžýáíéúů</b>
<b>More b tags</b>
<c>One followed by c tag</c>
<a>Last a tag</a>
<b>This time only with b tag, but this goes on and on</b>
</root>
And this XSLT:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes" method="xml" encoding="utf-8"/>
<xsl:strip-space elements="*"/>
<xsl:output method="xml" encoding="utf-8"/>
<xsl:key name="groupA" match="b|c|d" use="generate-id(preceding-sibling::a[1])" />
<xsl:key name="groupB" match="c|d" use="generate-id(preceding-sibling::b[1])"/>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="root">
<wrapperTest>
<xsl:apply-templates/>
</wrapperTest>
</xsl:template>
<xsl:template match="root">
<xsl:apply-templates select="#*|a"/>
<xsl:apply-templates select="#*|b"/>
</xsl:template>
<xsl:template match="a">
<xsl:copy>
<xsl:apply-templates select="key('groupA', generate-id())" />
</xsl:copy>
</xsl:template>
<xsl:template match="b">
<xsl:copy>
<xsl:apply-templates select="key('groupB', generate-id())" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The expected output is:
<wrapperTest>
<a>First text
<b>Text
<c>More text in c tag</c>
<d>There is even d tag</d>
</b>
</a>
<a>Another "a" test.
<b>ěščřžýáíéúů</b>
<b>More b tags
<c>One followed by c tag</c>
</b>
</a>
<a>Last a tag
<b>This time only with b tag, but this goes on and on</b>
</a>
</wrapperTest>
In the transformation I created are excessive copies created and I have no idea why. I guess that the isuue I am hiting upon is basic in its nature, but I cant figure it out.
The only limit for solution is, that preferably it should be in XSLT 1.0 (since the project is incorporated in python script with lxml). In the edge case, when this couldnt be achived with XSLT 1.0, I can accomodate for recent saxon version which removes any limitations ...
I have already looked at answers here, here and others, but most of them use either XSLT 2.0 or are very complicated for a beginner to knive through.
Final note: Ideally, proposed solution should be extensible in it nature, because the final form of my project should be also grouped by tag <c>, like so:
<wrapperTest>
<a>First text
<b>Text
<c>More text in c tag
<d>There is even d tag</d>
</c>
</b>
</a>
<a>Another "a" test.
<b>ěščřžýáíéúů</b>
<b>More b tags
<c>One followed by c tag</c>
</b>
</a>
<a>Last a tag
<b>This time only with b tag, but this goes on and on</b>
</a>
</wrapperTest>
Which I will happily do as learning excersize.
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="*"/>
<xsl:key name="b" match="b" use="generate-id(preceding-sibling::a[1])" />
<xsl:key name="cd" match="c|d" use="generate-id(preceding-sibling::b[1])"/>
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/root">
<wrapperTest>
<xsl:apply-templates select="a"/>
</wrapperTest>
</xsl:template>
<xsl:template match="a">
<xsl:copy>
<xsl:apply-templates/>
<xsl:apply-templates select="key('b', generate-id())" />
</xsl:copy>
</xsl:template>
<xsl:template match="b">
<xsl:copy>
<xsl:apply-templates/>
<xsl:apply-templates select="key('cd', generate-id())" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The recursive XSLT 2/3 for-each-group group-starting-with for all levels comes down to
<xsl:function name="mf:wrap" as="node()*">
<xsl:param name="input" as="node()*"/>
<xsl:for-each-group select="$input" group-starting-with="node()[node-name() = node-name($input[1])]">
<xsl:copy>
<xsl:sequence select="node(), mf:wrap(tail(current-group()))"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="root">
<xsl:copy>
<xsl:sequence select="mf:wrap(*)"/>
</xsl:copy>
</xsl:template>
https://xsltfiddle.liberty-development.net/gVhEaj8
I have a XML file where elements B are inside elements A and I want to move them up. From:
<?xml version="1.0" encoding="utf-8"?>
<root>
<A>
<C>Text</C>
Text again
More text
<D>Other text</D>
<B>Text again</B>
<C>No</C>
<D>May be</D>
<B>What?</B>
</A>
<A>
Something
<B>Nothing</B>
<D>Again</D>
<B>Content</B>
End
</A>
</root>
I would like to have:
<?xml version="1.0" encoding="utf-8"?>
<root>
<A>
<C>Text</C>
Text again
More text
<D>Other text</D>
</A>
<B>Text again</B>
<A>
<C>No</C>
<D>May be</D>
</A>
<B>What?</B>
<A>
Something
</A>
<B>Nothing</B>
<A>
<D>Again</D>
</A>
<B>Content</B>
<A>
End
</A>
</root>
The closest XSLT program I have is this:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs" version="1.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="A">
<xsl:for-each select="*">
<xsl:choose>
<xsl:when test="name()='B'">
<xsl:apply-templates select="."/>
</xsl:when>
<xsl:otherwise>
<xsl:element name="A">
<xsl:apply-templates select="."/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
It has two problems: it ignores text nodes (this is probably just a matter of adding |text() to the select="*") but, more important, it creates a element for each node while I would like them to stay together under one . For instance, the above stylesheet makes:
<A><C>No</C></A>
<A><D>May be</D></A>
where I want:
<A><C>No</C>
<D>May be</D></A>
In my XML files, are always direct children of , and there is no or nesting.
The main use case is producing HTML where UL and OL cannot be inside a P.
This question is related but not identical to xslt flattening out child elements in a DocBook para element (and may be also to Flatten xml hierarchy using XSLT
)
As I said in the comment to your question, this is not about moving elements up in hierarchy. It is about grouping nodes, and creating a new parent A element for each group determined by the dividing B element.
In XSLT 1.0 this can be achieved using a so-called sibling recursion:
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:template match="/root">
<xsl:copy>
<xsl:apply-templates select="A"/>
</xsl:copy>
</xsl:template>
<xsl:template match="A">
<xsl:copy>
<xsl:apply-templates select="node()[1][not(self::B)]" mode="sibling"/>
</xsl:copy>
<xsl:apply-templates select="B[1]" mode="sibling"/>
</xsl:template>
<xsl:template match="node()" mode="sibling">
<xsl:copy-of select="." />
<xsl:apply-templates select="following-sibling::node()[1][not(self::B)]" mode="sibling"/>
</xsl:template>
<xsl:template match="B" mode="sibling">
<xsl:copy-of select="." />
<xsl:if test="following-sibling::node()[normalize-space()]">
<A>
<xsl:apply-templates select="following-sibling::node()[1][not(self::B)]" mode="sibling"/>
</A>
<xsl:apply-templates select="following-sibling::B[1]" mode="sibling"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
An XSLT-1.0 solution - which is quite ugly - is the following. The output is as desired, but only for this simple MCVE. A general solution would be far more complicated as #michael.hor257k mentioned in the comments. Without more data it is unlikely to create a better solution in XSLT-1.0. Solutions for XSLT-2.0 and above may simplify this.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root">
<xsl:copy>
<xsl:for-each select="A">
<xsl:if test="normalize-space(text()[1])">
<A>
<xsl:copy-of select="text()[1]" />
</A>
</xsl:if>
<xsl:if test="preceding::*">
<xsl:copy-of select="B[1]" />
</xsl:if>
<A>
<xsl:copy-of select="C[1] | C[1]/following-sibling::text()[1] | D[1]" />
</A>
<xsl:if test="not(preceding::*)">
<xsl:copy-of select="B[1]" />
</xsl:if>
<A>
<xsl:copy-of select="C[2] | C[2]/following-sibling::text()[1]" />
<xsl:if test="D[2]">
<xsl:copy-of select="D[2]" />
</xsl:if>
</A>
<xsl:copy-of select="B[2]" />
<xsl:if test="normalize-space(text()[last()])">
<A>
<xsl:copy-of select="text()[last()]" />
</A>
</xsl:if>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Concerning the situation of
<A><C>No</C></A>
<A><D>May be</D></A>
It is handled appropriately in the above code. So its output is
<A>
<C>No</C>
<D>May be</D>
</A>
Easy in XSLT 2 or 3 with group-adjacent=". instance of element(B)" or group-adjacent="boolean(self::B)", here is an XSLT 3 example (XSLT 3 is supported by Saxon 9.8 or 9.9 on Java and .NET (https://sourceforge.net/projects/saxon/files/Saxon-HE/) and by Altova since 2017 releases):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="A">
<xsl:for-each-group select="node()" group-adjacent=". instance of element(B)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy select="..">
<xsl:apply-templates select="current-group()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/gWmuiKv
In XSLT 2 you need to spell out the <xsl:mode on-no-match="shallow-copy"/> as the identity transformation template and use xsl:element instead xsl:copy:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="A">
<xsl:for-each-group select="node()" group-adjacent=". instance of element(B)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:apply-templates select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<xsl:element name="{name(..)}" namespace="{namespace-uri(..)}">
<xsl:apply-templates select="current-group()"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>
</xsl:transform>
http://xsltransform.hikmatu.com/pPqsHT2
Any ideas of how the following problem can be solved would be highly appreciated.
INPUT:
<p>
<div>
Original<br/>This is the original <b>acid</b>, a hydroxy monocarboxylic <span class="hl1">acid</span>.
</div>
</p>
Desired Output:
<p>
<div>
Original<br/>This is the original <b>acid</b>, a hydroxy monocarboxylic <span class="hl1">acid</span>.
</div>
</p>
Attempt 1:
`<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output omit-xml-declaration="yes" indent="no" encoding="UTF-8"/>
<!--The identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="div">
<xsl:copy>
<xsl:value-of select="/" disable-output-escaping="no"/>
</xsl:copy>
</xsl:template>
`
Attempt2:
as an alternative, I thought of placing the child elements' content into a CDATA wrapper.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output omit-xml-declaration="yes" indent="no" encoding="UTF-8"/>
<!--The identity template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="div">
<xsl:copy>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:value-of select="/" />
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</xsl:copy>
</xsl:template>
But that does not give me what I want.
Anyone with a better idea? I'm using XSLT 2.0
Here is a suggestion using XSLT 3.0 serialize() as supported by Saxon 9.6 HE, PE and EE:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="div">
<xsl:copy>
<xsl:apply-templates mode="serialize"/>
</xsl:copy>
</xsl:template>
<xsl:template match="node()" mode="serialize">
<xsl:variable name="ser-params">
<output:serialization-parameters xmlns:output="http://www.w3.org/2010/xslt-xquery-serialization">
<output:omit-xml-declaration value="yes"/>
</output:serialization-parameters>
</xsl:variable>
<xsl:value-of select="serialize(., $ser-params/*)"/>
</xsl:template>
</xsl:stylesheet>
With older version of Saxon 9 you could use the extension function serialize, as shown in http://xsltransform.net/pPqsHTx:
<?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"
exclude-result-prefixes="xs">
<xsl:output name="inline" omit-xml-declaration="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="div">
<xsl:copy>
<xsl:apply-templates mode="serialize"/>
</xsl:copy>
</xsl:template>
<xsl:template match="node()" mode="serialize">
<xsl:value-of xmlns:saxon="http://saxon.sf.net/" select="saxon:serialize(., 'inline')"/>
</xsl:template>
</xsl:stylesheet>
Your second attempt should work if you change your xsl:value-of to an xsl:copy-of and tweak the select:
<xsl:template match="div">
<xsl:copy>
<xsl:text disable-output-escaping="yes"><![CDATA[</xsl:text>
<xsl:copy-of select="node()" />
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</xsl:copy>
</xsl:template>
I have this XML:
<root>
<tab name="Detail">
<section name="mysection">
<items level="1">
<Idx_name>9</Idx_name>
<Type>mytype</Type>
<item name="myname">
<Grams um="(g)">9,0</Grams>
<Pre-infusion>Max</Pre-infusion>
</item>
<Std._Mode>On</Std._Mode>
<Price>100</Price>
</items>
</section>
</tab>
</root>
and this XSLT:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items/*">
<xsl:choose>
<xsl:when test="not(name()='item')">
<xsl:attribute name="{name()}"><xsl:value-of select="."/></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Now, what I want is:
<root>
<tab name="Detail">
<section name="mysection">
<items level="1" Idx_name="9" Type="mytype" Std._Mode="On" Price="100">
<item name="myname">9,0Max</item>
</items>
</section>
</tab>
</root>
I obtain the error: "An attribute cannot be added after a child"
Unluckily, I can not change the order of elements in the node items of my original XML
How can I do it ?
Thanks
Ivan
Make sure you process the elements first you want to transform into attributes e.g. with XSLT 2.0 where you can process sequences which have a order you can simply do
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#*, node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items">
<xsl:copy>
<xsl:apply-templates select="#*, * except item, item"/>
</xsl:copy>
</xsl:template>
<xsl:template match="items/*[not(self::item)]">
<xsl:attribute name="{name()}" select="."/>
</xsl:template>
<xsl:template match="items/item">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:value-of select="."/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With XSLT 1.0 you would need to spell out several apply-templates in the order you want.
I'm struggling with trying to test to see if an element exists. If it doesn't, I'd like to add in a default value. Here's my XML
<records>
<record>
<InstanceData>
<instance>
<FirstName>Johhny</FirstName>
<LastName>Jenkins</LastName>
<AlbumCount>3</AlbumCount>
</instance>
</InstanceData>
</record>
<record>
<InstanceData>
<instance>
<FirstName>Art</FirstName>
<LastName>Tatum</LastName>
<AlbumCount>7</AlbumCount>
</instance>
</InstanceData>
</record>
<record>
<InstanceData>
<instance>
<FirstName>Count</FirstName>
<LastName>Basie</LastName>
</instance>
</InstanceData>
</record>
</records>
I'd like to be able to copy over existing values and set any record without the Album Count element to <AlbumCount>0</AlbumCount>. This is the xslt I've been working with but I think I'm some way off the mark.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="Records">
<xsl:for-each select="node()">
<xsl:choose>
<xsl:when test="name()='AlbumCount'">
<xsl:element name="AlbumCount">
<xsl:choose>
<xsl:when test="name()='AlbumCount'">
<xsl:copy-of select=".">
</xsl:copy-of>
</xsl:when>
<xsl:otherwise>
<AlbumCount>0</AlbumCount>
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select=".">
</xsl:copy-of>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Thanks for looking.
Try this:
<?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" omit-xml-declaration="no"/>
<!-- identity template -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="instance[not(AlbumCount)]">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<AlbumCount>0</AlbumCount>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Start with the identity transformation, then just handle the exception differently.
You test for the existance of an element simply with the elements name, for example:
<xsl:if test="not(AlbumCount)">
<AlbumCount>0</AlbumCount>
</xsl:if>
The simpler way to do what you want is to use the standard copy template combined with a special rule for places where AlbumCount elements need adding:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<!-- Standard copy template -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!-- Special template to add AlbumCount elements where required -->
<xsl:template match="records/record/InstanceData/instance">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
<xsl:if test="not(AlbumCount)">
<AlbumCount>0</AlbumCount>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>