Flattening with XSLT: I want to move one kind element one level - xslt

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

Related

Wrap node inside xslt element based on condition

I have to wrap certain nodes inside another element conditionally. In the below example I need to wrap CityName inside MyCity if city is Venezia. I have achieved it now, but i think there is better way of writing this without repeating the node. One way is to create a template and call that for the CityName, but is there any more elegant ways with fewer lines of code?
Input
<cities>
<city name="Milano"/>
<city name="Paris"/>
<city name="Munchen"/>
<city name="Lyon"/>
<city name="Venezia"/>
</cities>
My Xslt
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="city">
<xsl:choose>
<xsl:when test="#name='Venezia'">
<myCity>
<CityName>
<xsl:value-of select="#name"/>
</CityName>
</myCity>
</xsl:when>
<xsl:otherwise>
<CityName>
<xsl:value-of select="#name"/>
</CityName>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Identity Transform-->
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*"/>
</xsl:stylesheet>
Output I am looking for
<cities>
<CityName>Milano</CityName>
<CityName>Paris</CityName>
<CityName>Munchen</CityName>
<CityName>Lyon</CityName>
<myCity>
<CityName>Venezia</CityName>
</myCity>
</cities>
I think that all you need is:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="city[#name='Venezia']">
<myCity>
<xsl:next-match/>
</myCity>
</xsl:template>
<xsl:template match="city">
<CityName>
<xsl:value-of select="#name"/>
</CityName>
</xsl:template>
<!-- Identity Transform-->
<xsl:template match="node()">
<xsl:copy>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With XSLT 3 it boils down to
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:template match="city[#name]">
<CityName>{#name}</CityName>
</xsl:template>
<xsl:template match="city[#name = 'Venezia']">
<myCity>
<xsl:next-match/>
</myCity>
</xsl:template>
<xsl:mode on-no-match="shallow-copy" on-multiple-match="use-last"/>
</xsl:stylesheet>
xsl:next-match is also available in XSLT 2.0 but you would need to specify priority on a template instead of relying on xsl:mode on-multiple-match="use-last". Text value templates in XSLT 3 are just syntactic sugar that can be replace with xsl:value-of.

XMLT : replace values with values found in another xml

I have a file called ori.xml:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<container>
<elA>
<el1>value1</el1>
<el2>value2</el2>
</elA>
<elB>
<el3>value3</el3>
<el4>value4</el4>
<el5>value5</el5>
</elB>
<elC>
<el6>value5</el6>
</elC>
</container>
</root>
and another one called modifs.xml:
<?xml version="1.0" encoding="UTF-8"?>
<els>
<el2>newvalue2</el2>
<el5>newvalue5</el5>
</els>
and I would like to obtain result.xml:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<container>
<elA>
<el1>value1</el1>
<el2>newvalue2</el2>
</elA>
<elB>
<el3>value3</el3>
<el4>value4</el4>
<el5>newvalue5</el5>
</elB>
<elC>
<el6>value5</el6>
</elC>
</container>
</root>
I'm a beginner in XSLT.
So I started to write a stylesheet with which I'm able to change value2 into newvalue2:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="fileName" select="'modifs.xml'" />
<xsl:param name="modifs" select="document($fileName)" />
<xsl:param name="updateEl" >
<xsl:value-of select="$modifs/els/el2" />
</xsl:param>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//elA/el2">
<xsl:copy>
<xsl:apply-templates select="$updateEl" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
But now I have to modify this stylesheet to be able to know which elements are in modifs.xml and find them in ori.xml. I don't know how to do that. Could you help please ?
I would use a key:
<xsl:key name="ref-change" match="els/*" use="local-name()"/>
<xsl:template match="*[key('ref-change', local-name(), $modifs)]">
<xsl:copy-of select="key('ref-change', local-name(), $modifs)"/>
</xsl:template>
However, using the third argument for the key function is only supported in XSLT 2 and later thus if you use an XSLT 1 processor you need to move the logic into the template, that requires using for-each to "switch" the context document
<xsl:template match="*">
<xsl:variable name="this" select="."/>
<xsl:for-each select="$modifs">
<xsl:choose>
<xsl:when test="key('ref-change', local-name($this))">
<xsl:copy-of select="key('ref-change', local-name($this))"/>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="$this">
<xsl:call-template name="identity"/>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
Put name="identity" on your identity transformation template.

XSLT – creating a network from all children elements

With XSLT 2.0, I am trying to create a list of relations between all children of given elements, in a document such as:
<doc>
<part1>
<name>John</name>
<name>Paul</name>
<name>George</name>
<name>Ringo</name>
<place>Liverpool</place>
</part1>
<part2>
<name>Romeo</name>
<name>Romeo</name>
<name>Juliet</name>
<fam>Montague</fam>
<fam>Capulet</fam>
</part2>
</doc>
The result I would like to obtain, ideally by conflating and weighing the identical relations, would be (in whatever order) something like:
<doc>
<part1>
<rel><name>John</name><name>Paul</name></rel>
<rel><name>John</name><name>George</name></rel>
<rel><name>John</name><name>Ringo</name></rel>
<rel><name>Paul</name><name>George</name></rel>
<rel><name>Paul</name><name>Ringo</name></rel>
<rel><name>George</name><name>Ringo</name></rel>
<rel><name>John</name><place>Liverpool</place></rel>
<rel><name>Paul</name><place>Liverpool</place></rel>
<rel><name>George</name><place>Liverpool</place></rel>
<rel><name>Ringo</name><place>Liverpool</place></rel>
</part1>
<part2>
<rel weight="2"><name>Romeo</name><name>Juliet</name></rel>
<rel weight="2"><name>Romeo</name><fam>Montague</fam></rel>
<rel weight="2"><name>Romeo</name><fam>Capulet</fam></rel>
<rel><name>Juliet</name><fam>Montague</fam></rel>
<rel><name>Juliet</name><fam>Capulet</fam></rel>
<rel><fam>Montague</fam><fam>Capulet</fam></rel>
</part2>
</doc>
—but I'm not sure how to proceed. Many thanks in advance for your help.
You still haven't explained the logic that needs to be applied here, so this is based largely on a guess:
XSLT 2.0
<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="/">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="doc/*">
<!-- first pass-->
<xsl:variable name="unique-items">
<xsl:for-each-group select="*" group-by="concat(name(), '|', .)">
<item name="{name()}" count="{count(current-group())}" value="{.}"/>
</xsl:for-each-group>
</xsl:variable>
<!-- output -->
<xsl:copy>
<xsl:for-each select="$unique-items/item">
<xsl:variable name="left" select="."/>
<xsl:for-each select="following-sibling::item">
<xsl:variable name="weight" select="$left/#count * #count" />
<rel>
<xsl:if test="$weight gt 1">
<xsl:attribute name="weight" select="$weight"/>
</xsl:if>
<xsl:apply-templates select="$left | ." />
</rel>
</xsl:for-each>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:element name="{#name}">
<xsl:value-of select="#value"/>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
The idea here is to remove duplicates in the first pass, then enumerate all combinations in the second (final) pass. The weight is computed by multiplying the number of occurrences of each member of a combination pair and shown only when it exceeds 1.
At least the combinatoric part of your problem could be solved with the following XSLT script. It does not solve the elimination of duplicates, but that could possibly be done in a second transformation.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- standard copy template -->
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="doc/*">
<xsl:copy>
<xsl:variable name="l" select="./*"/>
<xsl:for-each select="$l">
<xsl:variable name="a" select="."/>
<xsl:variable name="posa" select="position()"/>
<xsl:variable name="namea" select="name()"/>
<xsl:for-each select="$l">
<xsl:if test="position() > $posa and (. != $a or name() != $namea)">
<rel>
<xsl:copy-of select="$a"/>
<xsl:copy-of select="."/>
</rel>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to the first part of your example, this produces:
<part1>
<rel><name>John</name><name>Paul</name></rel>
<rel><name>John</name><name>George</name></rel>
<rel><name>John</name><name>Ringo</name></rel>
<rel><name>John</name><place>Liverpool</place></rel>
<rel><name>Paul</name><name>George</name></rel>
<rel><name>Paul</name><name>Ringo</name></rel>
<rel><name>Paul</name><place>Liverpool</place></rel>
<rel><name>George</name><name>Ringo</name></rel>
<rel><name>George</name><place>Liverpool</place></rel>
<rel><name>Ringo</name><place>Liverpool</place></rel>
</part1>
Which seems about correct. If have no idea if the duplicate elimination (or weighting, as you call it) could be done in the same transformation.

XSL Venetian Blind

Could anyone help me with an XSLT to convert an XSD from Venetian Blind to Russian Doll design? I read an article on Stack Overflow about the reverse: Russian doll to Venetian blind xsl transformation
With a lot of help from a colleague, I finally got an answer. It may not be elegant, but it meets my immediate needs, here it is:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
exclude-result-prefixes='exsl'
version="2.0"
xmlns:com="http://canaldigital.com/tsi/XSD/V5.00"
xmlns:exsl="http://exslt.org/common"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="xsd:complexType">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template name="complexTypeElemEmbedded">
<xsl:param name="typeName"></xsl:param>
<xsl:variable name="typeNameNoNS" select="substring-after($typeName, ':')" />
<xsd:complexType>
<xsl:apply-templates select="//xsd:complexType[#name=$typeNameNoNS]/node()"></xsl:apply-templates>
</xsd:complexType>
</xsl:template>
<xsl:template match="xsd:element[#type]">
<xsl:choose>
<!-- All simple types have a name ending in the literal 'Type' -->
<xsl:when test="(ends-with(#type, 'Type'))">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:when>
<!-- this picks up the global complex types -->
<xsl:otherwise>
<xsl:copy>
<xsl:copy-of select="#*[(name()!='type')]"/>
<xsl:call-template name="complexTypeElemEmbedded">
<xsl:with-param name="typeName" select="#type"/>
</xsl:call-template>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

How to Ignore specifc element value using value-of

Below my input
<root>
<text>
<bold>Co<csc>lorado DivIs</csc>IoN</bold>
</text>
<text>
fi<csc>ve and a ha</csc>lf <x>abc</x>
</text>
</root>
Here is my xslt my implemntation (xslt verions-1.0)
<xsl:for-each select="/root/text">
<xsl:value-of select="./child::*[local-name(.)!='x']" />
<xsl:text> </xsl:text>
</xsl:for-each>
correct output look like below, should ignore only 'x' element value.
Colorado DivIsIoN five and a half
The output I am getting is with missing current element text.
Colorado DivIsIoN ve and a ha
Try it this way?
<xsl:template match="/">
<xsl:for-each select="root/text">
<xsl:for-each select=".//text()[not (parent::x)]">
<xsl:value-of select="." />
</xsl:for-each>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
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="html" encoding="UTF-8" indent="yes"/>
<xsl:template match="root">
<xsl:apply-templates select="#*|node()"/>
</xsl:template>
<xsl:template match="text/x"/>
<xsl:template match="#* | node()">
<xsl:apply-templates select="#* | node()"/>
</xsl:template>
<xsl:template match="text()">
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>