Remove all elements with a specific namespace in xslt - xslt

I have an xml which I'd like to change the namespace of most elements, remove some specific element names and also remove elements which contains a specific namespace. Example of such an xml
<root xmlns="somenamespace">
<elem1>sometext</eleme1>
<ns0:elem2 xmlns:ns0="othernamespace">
<ns1:elem3 xmlns:ns1="thirdnamespace" />
</ns0:elem2>
<elem4>sometext</elem4>
</root>
I am trying to use the following xslt:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="*[namespace-uri() = 'somenamespace']">
<xsl:choose>
<!-- change element name from root to root2 -->
<xsl:when test="local-name(.)='root'">
<xsl:element name="root2" namespace="mynamespace">
<xsl:apply-templates select="#* | node()" />
</xsl:element>
</xsl:when>
<!-- skip these elements that are not in root2 -->
<xsl:when test="local-name(.)='elem1'" />
<xsl:when test="namespace-uri()='othernamespace'" />
<!-- Copy other elemnts -->
<xsl:otherwise>
<xsl:element name="{name()}" namespace="mynamespace">
<xsl:apply-templates select="#* | node()" />
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Copy the rest -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()" />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
The output xml should be
<root2 xmlns="mynamespace">
<elem4>sometext</elem4>
</root2>
However the result is
<root2 xmlns="mynamespace" xmlns:ns0="othernamespace">
<ns0:elem2>
<ns1:elem3 xmlns:ns1="thirdnamespace" />
</ns0:elem2>
<elem4>sometext</elem4>
</root2>
It seems that most elements of the xslt are working except the one that is supposed to remove all elements of a specific namespace. Is there anything wrong in the xslt above?

Your first template is only matching elements in the somenamespace namespace. The other namespaces (othernamespace,thirdnamespace) are matched by the identity transform (last template) and are output as-is.
To strip all elements that aren't in the somenamespace namespace, add this template:
<xsl:template match="*[not(namespace-uri()='somenamespace')]" priority="1"/>

Related

XSLT: create node if does not exists

I know similar questions are already there but none of them seem to work for me.
So shortly, I have XML file with tag "Lokal" that in most cases does not appear but it should. Not making things easier: I also need to change a name of "Lokal" to let's say "Lokal_test". My goal is modify node name(if exists) or create it and rename (if does not exists).
Data from XML will be imported to MS Access data so they need to match perfectly with table...
Sample XML:
<Dane>
<InformacjeOWpisie>
<DaneAdresowe>
<AdresGlownegoMiejscaWykonywaniaDzialalnosci>
<Budynek>3a</Budynek>
<Wojewodztwo>podlaskie</Wojewodztwo>
</AdresGlownegoMiejscaWykonywaniaDzialalnosci>
</DaneAdresowe>
</InformacjeOWpisie>
<InformacjeOWpisie>
<DaneAdresowe>
<AdresGlownegoMiejscaWykonywaniaDzialalnosci>
<Budynek>8r</Budynek>
<Lokal>2</Lokal>
<Wojewodztwo>mazowieckie</Wojewodztwo>
</AdresGlownegoMiejscaWykonywaniaDzialalnosci>
</DaneAdresowe>
</InformacjeOWpisie>
</Dane>
Desired output:
<Dane>
<InformacjeOWpisie>
<DaneAdresowe>
<AdresGlownegoMiejscaWykonywaniaDzialalnosci>
<Budynek>3a</Budynek>
<Lokal_test/>
<Wojewodztwo>podlaskie</Wojewodztwo>
</AdresGlownegoMiejscaWykonywaniaDzialalnosci>
</DaneAdresowe>
</InformacjeOWpisie>
<InformacjeOWpisie>
<DaneAdresowe>
<AdresGlownegoMiejscaWykonywaniaDzialalnosci>
<Budynek>8r</Budynek>
<Lokal_test>2</Lokal_test>
<Wojewodztwo>mazowieckie</Wojewodztwo>
</AdresGlownegoMiejscaWykonywaniaDzialalnosci>
</DaneAdresowe>
</InformacjeOWpisie>
</Dane>
This question(XSLT: create node if not exists seemed to be the awnser to my problems but when trying to use it does not work.
Not sure why?
<xsl:template match="InformacjeOWpisie/DaneAdresowe/AdresGlownegoMiejscaWykonywaniaDzialalnosci/Lokal">
<Lokal_test>
<xsl:apply-templates select="#*|node()" />
</Lokal_test>
</xsl:template>
EDIT:
When I get rid of parent Lokal_test dissapears. I use below code to say "bye bye" to parent:
<xsl:template match="InformacjeOWpisie/DaneAdresowe/AdresGlownegoMiejscaWykonywaniaDzialalnosci">
<xsl:apply-templates select="#*|node()" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="AdresGlownegoMiejscaWykonywaniaDzialalnosci/Budynek">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
<xsl:choose>
<xsl:when test="exists(following-sibling::Lokal)">
<Lokal_test>
<xsl:value-of select="following-sibling::Lokal"/>
</Lokal_test>
</xsl:when>
<xsl:when test="not(following-sibling::Lokal)">
<xsl:element name="Lokal_test"/>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="Lokal"/>
You approach was right, but incomplete. You only created the new Local_test element.
So try these two templates in combination with the indentity template:
<!-- Handles the replacement of the 'Lokal' element -->
<xsl:template match="AdresGlownegoMiejscaWykonywaniaDzialalnosci/Lokal">
<Lokal_test>
<xsl:apply-templates select="node()|#*" />
</Lokal_test>
</xsl:template>
<!-- Creates a new 'Lokal_test' element if no 'Lokal' element exists -->
<xsl:template match="AdresGlownegoMiejscaWykonywaniaDzialalnosci[not(Lokal)]">
<xsl:copy>
<xsl:apply-templates select="node()/following-sibling::Wojewodztwo/preceding-sibling::*|#*" /> <!-- Copy nodes before 'Wojewodztwo' -->
<Lokal_test />
<xsl:apply-templates select="Wojewodztwo|Wojewodztwo/following-sibling::*|#*" /> <!-- Copy nodes after 'Wojewodztwo' (including) -->
</xsl:copy>
</xsl:template>
The second template puts the Lokal_test element before the Wojewodztwo element and copies the surrounding nodes.

Replace all instances of a string in XML with ****

I have a XSL that needs to filter out specific data found in the XML.
Somewhere in my XML there will be a node like:
<id root="2.16.840.1.113883.3.51.1.1.6.1" extension="9494949494949" />
The XSL I have below deletes the extension node and adds a nullFlavor="MSK" to the node.
What I need to do now, is take the value from the extension node, and search the entire XML document for that value, and replace it with **.
But I'm not sure how to take the extension attribute, and find all instances of that value in the XML (they could be burried in text and inside attributes) and turn them into ** (4 *).
The example below is just an example. I cannot hard code the XSL to look at specific nodes, it needs to look through all text / attribute text in the xml (reason for this is there are 5+ different versions of XML that this will be applied to).
I need to find the Extension in the node, then replace (delete really) that value from the rest of the XML. I'm looking for a 1 solution fits all messages, so a global search->wipe of the Extension value.
Example:
<identifiedPerson classCode="IDENT">
<id root="2.16.840.1.113883.3.51.1.1.6.1" extension="9494949494949" displayable="true" />
<addr use="PHYS">
<city>KAMLOOPS</city>
<country>CA</country>
<postalCode>V1B3C1</postalCode>
<state>BC</state>
<streetAddressLine>1A</streetAddressLine>
<streetAddressLine>2A</streetAddressLine>
<streetAddressLine>9494949494949</streetAddressLine>
<streetAddressLine>4A</streetAddressLine>
</addr>
<note text="9494949494949 should be stars"/>
Should be (The below XSLT already masks the extension in the node with the matching OID).
<identifiedPerson classCode="IDENT">
<id root="2.16.840.1.113883.3.51.1.1.6.1" nullFlavor="MSK" displayable="true" />
<addr use="PHYS">
<city>KAMLOOPS</city>
<country>CA</country>
<postalCode>V1B3C1</postalCode>
<state>BC</state>
<streetAddressLine>1A</streetAddressLine>
<streetAddressLine>2A</streetAddressLine>
<streetAddressLine>****</streetAddressLine>
<streetAddressLine>4A</streetAddressLine>
</addr>
<note text="**** should be stars"/>
Any help would be appreciated.
I am able to use XSL 2.0
I have the current XSL.IT works fine. It matches any tag where the root is '2.16.840.1.113883.3.51.1.1.6.1', kills all attributes and adds a nullFlavor="MSK". However, this will not search the entire XML for that same #.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="attrToKeep" select="'root'" />
<xsl:template match="* | node()">
<xsl:copy>
<xsl:apply-templates select="node()|#*" />
</xsl:copy>
</xsl:template>
<xsl:template match="#*">
<xsl:choose>
<xsl:when test="../#root = '2.16.840.1.113883.3.51.1.1.6.1'">
<xsl:copy-of select=".[contains($attrToKeep, name())]" />
<xsl:attribute name="nullFlavor">MSK</xsl:attribute>
<!-- Need some way to use the value found in this node and hide the extension -->
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Any help would be appreciated.
Thanks,
Try using a variable to hold the value of the text to be replaced. Like this:
<xsl:variable
name="rootVar"
select="//*[#root = '2.16.840.1.113883.3.51.1.1.6.1']/#extension" />
And then you should just be able to use the replace function to replace them.
<xsl:template match="'//#*' | text()">
<xsl:sequence select="replace(., $rootVar, '****')"/>
</xsl:template>
The XSLT 2.0 stylesheet
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:param name="replacement" select="'****'"/>
<xsl:param name="new" select="'MKS'"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="identifiedPerson">
<xsl:copy>
<xsl:apply-templates select="#* , node()">
<xsl:with-param name="to-be-replaced" select="id/#extension" tunnel="yes"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="identifiedPerson//text()">
<xsl:param name="to-be-replaced" tunnel="yes"/>
<xsl:sequence select="replace(., $to-be-replaced, $replacement)"/>
</xsl:template>
<xsl:template match="identifiedPerson//#*">
<xsl:param name="to-be-replaced" tunnel="yes"/>
<xsl:attribute name="{name()}" namespace="{namespace-uri()}" select="replace(., $to-be-replaced, $replacement)"/>
</xsl:template>
<xsl:template match="identifiedPerson/id">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="nullFlavor" select="$new"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="identifiedPerson/id/#extension"/>
</xsl:stylesheet>
transforms
<identifiedPerson classCode="IDENT">
<id root="2.16.840.1.113883.3.51.1.1.6.1" extension="9494949494949" displayable="true" />
<addr use="PHYS">
<city>KAMLOOPS</city>
<country>CA</country>
<postalCode>V1B3C1</postalCode>
<state>BC</state>
<streetAddressLine>1A</streetAddressLine>
<streetAddressLine>2A</streetAddressLine>
<streetAddressLine>9494949494949</streetAddressLine>
<streetAddressLine>4A</streetAddressLine>
</addr>
<note text="9494949494949 should be stars"/>
</identifiedPerson>
with Saxon 9.4 into
<?xml version="1.0" encoding="UTF-8"?><identifiedPerson classCode="IDENT">
<id root="2.16.840.1.113883.3.51.1.1.6.1" displayable="true" nullFlavor="MKS"/>
<addr use="PHYS">
<city>KAMLOOPS</city>
<country>CA</country>
<postalCode>V1B3C1</postalCode>
<state>BC</state>
<streetAddressLine>1A</streetAddressLine>
<streetAddressLine>2A</streetAddressLine>
<streetAddressLine>****</streetAddressLine>
<streetAddressLine>4A</streetAddressLine>
</addr>
<note text="**** should be stars"/>
</identifiedPerson>
So for the sample it solves that problem I think. I am not sure whether there can be more context around that sample and whether you want to change values outside of the identifiedPerson element as well or don't want to change them (which above stylesheet does). If other elements also need to be changed consider to post longer input and wanted result samples to illustrate and also explain what determines the node where the value to be replaced is found.
[edit]
Based on your comment I adapted the stylesheet, it now has a parameter to pass in a id (e.g. 2.16.840.1.113883.3.51.1.1.6.1), then it looks for an element of any name with a root attribute having that passed in id value and replaces the extension attribute value found in all attributes and all text nodes found in the document. Furthermore a nullFlavor attribute is added to the element with the id and its extension attribute is removed.
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:param name="root-id" select="'2.16.840.1.113883.3.51.1.1.6.1'"/>
<xsl:variable name="to-be-replaced" select="//*[#root = $root-id]/#extension"/>
<xsl:param name="replacement" select="'****'"/>
<xsl:param name="new" select="'MKS'"/>
<xsl:template match="comment() | processing-instruction()">
<xsl:copy/>
</xsl:template>
<xsl:template match="*">
<xsl:copy>
<xsl:apply-templates select="#* , node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:sequence select="replace(., $to-be-replaced, $replacement)"/>
</xsl:template>
<xsl:template match="#*">
<xsl:attribute name="{name()}" namespace="{namespace-uri()}" select="replace(., $to-be-replaced, $replacement)"/>
</xsl:template>
<xsl:template match="*[#root = $root-id]">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="nullFlavor" select="$new"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[#root = $root-id]/#extension"/>
</xsl:stylesheet>

How do I Handle multiple XSLT copy statements on same match?

Are there any XSLT statements that will execute in consideration of other XSLT statements within the same stylesheet?
For example, if I have two copy statements matched to the same node (but only desire one copied node that contains the modifications declared in BOTH copy statements) is there a statement that will do this?
Assume that I cannot put all the transformations in one copy node, but instead have to use two or more.
---Clearer example---
//XML
<toy></toy>
//XSLT
<xsl:template match="toy">
<xsl:copy>
<xsl:attribute name="label">SOME TOY</xsl:attribute>
</xsl:copy>
</xsl:template>
<xsl:template match="toy">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:element name="range">
<xsl:element name="min">200001</xsl:element>
<xsl:element name="max">999999</xsl:element>
</xsl:element>
</xsl:copy>
</xsl:template>
My desired result would be a new toy node that is copied to a new file that has both things applied to it, so something like:
<toy label='SOME TOY'>
<range>
<min>200001</min>
<max>999999</max>
</range>
</toy>
Not two different copies
Is this possible? Is there some way I can redo the first template so that will make this one outcome?
There is rule in XSLT specification, which forbid this - Conflict Resolution for Template Rules.
If node fits several templates - only one template will be executed - in relation to the template import precedence, priority or document order, etc.
But you can separate it with named templates:
<xsl:template match="toy">
<xsl:call-template name="toyAttribute" />
<xsl:call-template name="toyElements" />
</xsl:template>
<xsl:template name="toyAttribute">
<xsl:copy>
<xsl:attribute name="label">SOME TOY</xsl:attribute>
</xsl:copy>
</xsl:template>
<xsl:template name="toyElements">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:element name="range">
<xsl:element name="min">200001</xsl:element>
<xsl:element name="max">999999</xsl:element>
</xsl:element>
</xsl:copy>
</xsl:template>
Update:
If you asking about only updating <toy> node with attribute and elements you don't need separate templates:
<!--toy template -->
<xsl:template match="/toys/toy">
<!--copy toy node with namespaces -->
<xsl:copy>
<!-- copy toy node attributes -->
<xsl:apply-templates select="#*" />
<!-- add new attribute or xsl:call-template name="toyAttribute"-->
<xsl:attribute name="label">SOME TOY</xsl:attribute>
<!-- copy toy node child elements -->
<xsl:apply-templates select="node()" />
<!-- add new elements - or xsl:call-template name="toyElements"-->
<xsl:element name="range">
<xsl:element name="min">200001</xsl:element>
<xsl:element name="max">999999</xsl:element>
</xsl:element>
</xsl:copy>
</xsl:template>
<!--Copy node content -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
which for XML:
<?xml version="1.0" encoding="UTF-8"?>
<toys>
<toy name="a">
<toy-part/>
</toy>
<toy name="b">
<toy-part/>
</toy>
</toys>
will give following result:
<?xml version="1.0" encoding="utf-8"?><toys>
<toy name="a" label="SOME TOY">
<toy-part/>
<range>
<min>200001</min>
<max>999999</max>
</range>
</toy>
<toy name="b" label="SOME TOY">
<toy-part/>
<range>
<min>200001</min>
<max>999999</max>
</range>
</toy>
</toys>

Merging adjacent nodes of same type (XSLT 1.0)

Is it possible to merge every sequence of nodes of the same specified type? ('aaa' in this case) (not just first occurrence of sequence)
Here is my XML input:
<block>
<aaa>text1</aaa>
<aaa>text2</aaa>
<aaa><xxx>text3</xxx></aaa>
<bbb>text4</bbb>
<aaa>text5</aaa>
<bbb><yyy>text6</yyy></bbb>
<bbb>text7</bbb>
<aaa>text8</aaa>
<aaa><zzz>text9</zzz></aaa>
<aaa>texta</aaa>
</block>
And I want following output:
<block>
<aaa>text1text2<xxx>text3</xxx></aaa>
<bbb>text4</bbb>
<aaa>text5</aaa>
<bbb><yyy>text6</yyy></bbb>
<bbb>text7</bbb>
<aaa>text8<zzz>text9</zzz>texta</aaa>
</block>
Any help appreciated
Here is another way to do this.
First, match on all the child nodes of the block element
<xsl:template match="block/child::*">
Next, check if the element's most direct sibling has a different name, indicating this is the first of one or more adjacent elements:
<xsl:if test="local-name(preceding-sibling::*[position()=1]) != $name">
If so, you can copy that node. Then, you need to copy of following siblings with the same name. I did this by recursively calling a template on each immediately following sibling with the same name
<xsl:apply-templates select="following-sibling::*[1][local-name()=$name]" mode="next"/>
Putting this all together gives
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<!-- Match children of the block element -->
<xsl:template match="block/child::*">
<xsl:variable name="name" select="local-name()"/>
<!-- Is this the first element in a sequence? -->
<xsl:if test="local-name(preceding-sibling::*[position()=1]) != $name">
<xsl:copy>
<xsl:apply-templates />
<!-- Match the next sibling if it has the same name -->
<xsl:apply-templates select="following-sibling::*[1][local-name()=$name]" mode="next"/>
</xsl:copy>
</xsl:if>
</xsl:template>
<!-- Recursive template used to match the next sibling if it has the same name -->
<xsl:template match="block/child::*" mode="next">
<xsl:variable name="name" select="local-name()"/>
<xsl:apply-templates />
<xsl:apply-templates select="following-sibling::*[1][local-name()=$name]" mode="next"/>
</xsl:template>
<!-- Template used to copy a generic node -->
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Assuming you only have one block, Muenchian method is the most optimized way to do this:
<!-- group nodes by name -->
<xsl:key name="block-children-by-name" match="block/*" use="name()"/>
<!-- for nodes that aren't first in their group, no output -->
<xsl:template match="block/*" />
<!-- for nodes that are first in their group, combine group children and output -->
<xsl:template match="block/*[generate-id() =
generate-id(key('block-children-by-name', name())[1])]">
<xsl:copy>
<xsl:copy-of select="key('block-children-by-name', name())/*"/>
</xsl:copy>
</xsl:template>
Note that this only merges the child nodes, and not e.g. any attributes that may occur on aaa and bbb themselves.
Here's another approach, without using recursive templates.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- identity transform -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="aaa">
<xsl:if test="not(preceding-sibling::*[1]/self::aaa)">
<xsl:variable name="following"
select="following-sibling::aaa[
not(preceding-sibling::*[
not(self::aaa) and
not(following-sibling::aaa = current())
])
]"/>
<xsl:copy>
<xsl:apply-templates select="$following/#*"/>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select="node()"/>
<xsl:apply-templates select="$following/node()"/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The rather convoluted XPath expression for selecting the following sibling aaa nodes which are merged with the current one:
following-sibling::aaa[ # following 'aaa' siblings
not(preceding-sibling::*[ # if they are not preceded by
not(self::aaa) and # a non-'aaa' node
not(following-sibling::aaa = current()) # after the current node
])
]

how to merge element using xslt?

I have an reference type of paragraph with element.
Example
Input file:
<reference>
<emph type="bold">Antony</emph><emph type="bold">,</emph> <emph type="bold">R.</emph>
<emph type="bold">and</emph> <emph type="bold">Micheal</emph><emph type="bold">,</emph> <emph type="bold">V.</emph>
<emph type="italic">reference title</emph></reference>
Output received now:
<p class="reference"><strong>Antony</strong><strong>,</strong> <strong>R.</strong>
<strong>and</strong> <strong>Micheal</strong><strong>,</emph>
<emph type="bold">V.</strong> <em>reference title></em></p>
Required output file:
<p class="reference"><strong>Antony, R. and Micheal, V.</strong> <em>reference title</em></p>
My xslt scripts:
<xsl:template match="reference">
<p class="reference"><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="emph">
<xsl:if test="#type='bold'">
<strong><xsl:apply-templates/></strong>
</xsl:if>
<xsl:if test="#type='italic'">
<em><xsl:apply-templates/></em>
</xsl:if>
</xsl:template>
What needs to be corrected in xslt to get the <strong> element single time like the required output file?
Please advice anyone..
By,
Antny.
This is an XSLT 1.0 solution:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="xml" encoding="utf-8" />
<!-- the identity template copies everything verbatim -->
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
<!-- this matches the first <emph> nodes of their kind in a row -->
<xsl:template match="emph[not(#type = preceding-sibling::emph[1]/#type)]">
<xsl:variable name="elementname">
<xsl:choose>
<xsl:when test="#type='bold'">strong</xsl:when>
<xsl:when test="#type='italic'">em</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:if test="$elementname != ''">
<!-- the first preceding node with a different type is the group separator -->
<xsl:variable
name="boundary"
select="generate-id(preceding-sibling::emph[#type != current()/#type][1])
" />
<xsl:element name="{$elementname}">
<!-- select all <emph> nodes of the row with the same type... -->
<xsl:variable
name="merge"
select=". | following-sibling::emph[
#type = current()/#type
and
generate-id(preceding-sibling::emph[#type != current()/#type][1]) = $boundary
]"
/>
<xsl:apply-templates select="$merge" mode="text" />
</xsl:element>
</xsl:if>
</xsl:template>
<!-- default: keep <emph> nodes out of the identity template mechanism -->
<xsl:template match="emph" />
<!-- <emph> nodes get their special treatment here -->
<xsl:template match="emph" mode="text">
<!-- effectively, this copies the text node via the identity template -->
<xsl:apply-templates />
<!-- copy the first following node - if it is a text node
(this is to get interspersed spaces into the output) -->
<xsl:if test="
generate-id(following-sibling::node()[1])
=
generate-id(following-sibling::text()[1])
">
<xsl:apply-templates select="following-sibling::text()[1]" />
</xsl:if>
</xsl:template>
</xsl:stylesheet>
It results in:
<reference>
<strong>Antony, R. and Micheal, V.</strong>
<em>reference title</em>
</reference>
I'm not overly happy with
<xsl:variable
name="merge"
select=". | following-sibling::emph[
#type = current()/#type
and
generate-id(preceding-sibling::emph[#type != current()/#type][1]) = $boundary
]"
/>
if someone has a better idea, please tell me.
Here is my method, which uses recursive calls of a template to match elements with the same type.
It first matchs the first 'emph' element, and them recursively calls a template matching 'emph' elements of the same type. Next, it repeats the process matching the next 'emph' element of a type different to the one currently matched.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8"/>
<!-- Match root element -->
<xsl:template match="reference">
<p class="reference">
<!-- Match first emph element -->
<xsl:apply-templates select="emph[1]"/>
</p>
</xsl:template>
<!-- Used to match first occurence of an emph element for any type -->
<xsl:template match="emph">
<xsl:variable name="elementname">
<xsl:if test="#type='bold'">strong</xsl:if>
<xsl:if test="#type='italic'">em</xsl:if>
</xsl:variable>
<xsl:element name="{$elementname}">
<xsl:apply-templates select="." mode="match">
<xsl:with-param name="type" select="#type"/>
</xsl:apply-templates>
</xsl:element>
<!-- Find next emph element with a different type -->
<xsl:apply-templates select="following-sibling::emph[#type!=current()/#type][1]"/>
</xsl:template>
<!-- Used to match emph elements of a specific type -->
<xsl:template match="*" mode="match">
<xsl:param name="type"/>
<xsl:if test="#type = $type">
<xsl:value-of select="."/>
<xsl:apply-templates select="following-sibling::*[1]" mode="match">
<xsl:with-param name="type" select="$type"/>
</xsl:apply-templates>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Where this currently fails though, is that it doesn't match the whitespace in between the 'emph' elements.