xslt pattern match transformation - xslt

How can I transform the following XML with XSLT from this:
<root>
<list>
<item label="21(1)">some text</item>
<item label="(2)">some text</item>
</list>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</root>
to this:
<root>
<list label="21">
<item label="(1)">some text</item>
<item label="(2)">some text</item>
</list>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</root>
So, if there is a number before a parenthesis on the label attribute of the first item, that number needs to be aded as the value of the label attribute for the parent list item.
The pattern to match the attribute would be something like:
/(\d+)\([^\)]+\)/

As mentioned by Nikolaus you can use the substring-before and substring-after XPath functions. A sample XSL transformation would look like 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" indent="yes"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="list">
<list>
<xsl:variable name="prefix" select="substring-before(./item/#label, '(')" />
<xsl:if test="$prefix != '' and number($prefix)">
<xsl:attribute name="label">
<xsl:value-of select="substring-before(./item/#label, '(')"/>
</xsl:attribute>
</xsl:if>
<xsl:apply-templates />
</list>
</xsl:template>
<xsl:template match="item">
<item>
<xsl:attribute name="label">
<xsl:variable name="prefix" select="substring-before(#label, '(')" />
<xsl:choose>
<xsl:when test="$prefix != '' and number($prefix)">
<xsl:value-of select="concat('(', substring-after(#label, '('))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="#label"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:apply-templates />
</item>
</xsl:template>
</xsl:stylesheet>

you can use the xslt function substring-before to get the substring befor '('

This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item[1][boolean(number(substring-before(#label,'(')))]">
<xsl:attribute name="label">
<xsl:value-of select="substring-before(#label,'(')"/>
</xsl:attribute>
<xsl:call-template name="identity"/>
</xsl:template>
<xsl:template match="item[1]/#label[boolean(number(substring-before(.,'(')))]">
<xsl:attribute name="label">
<xsl:value-of select="concat('(',substring-after(.,'('))"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Output:
<root>
<list label="21">
<item label="(1)">some text</item>
<item label="(2)">some text</item>
</list>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</root>
Edit: Compact predicate.
Edit 2: Test number before parentesis. Explicity strip white space only nodes.

This XSLT 1.0 transformation:
<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:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"list[number(substring-before(item[1]/#label, '('))
=
number(substring-before(item[1]/#label, '('))
]">
<list label="{substring-before(item[1]/#label, '(')}">
<xsl:apply-templates select="node()|#*"/>
</list>
</xsl:template>
<xsl:template match=
"item[1]/#label[number(substring-before(., '('))
=
number(substring-before(., '('))
]">
<xsl:attribute name="label">
<xsl:value-of select="concat('(',substring-after(.,'('))"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<list>
<item label="21(1)">some text</item>
<item label="(2)">some text</item>
</list>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</root>
produces the wanted, correct result:
<root>
<list label="21">
<item label="(1)">some text</item>
<item label="(2)">some text</item>
</list>
<list>
<item label="a">some text</item>
<item label="b">some text</item>
</list>
</root>

Related

Generate XML and JSON file using single XSLT

I already have an XSLT which takes XML input, transforms it and gives me XML output.
But is there a way where I can use same XSLT, and fetch the transformed XML output and convert it into JSON.
Here is a small and artificial example that you could use as your starting point:
Input XML
<root>
<item id="1">
<name>Alpha</name>
<value>101</value>
</item>
<item id="2">
<name>Bravo</name>
<value>2.25</value>
</item>
<item id="3">
<name>Charlie</name>
<value>33</value>
</item>
</root>
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="*"/>
<xsl:template match="/">
<xsl:variable name="output-xml">
<xsl:apply-templates/>
</xsl:variable>
<!-- write XML to output file -->
<xsl:copy-of select="$output-xml"/>
<!-- produce a JSON file as additional output -->
<xsl:result-document href="json.txt" method="text">
<xsl:apply-templates select="$output-xml" mode="json"/>
</xsl:result-document>
</xsl:template>
<xsl:template match="root">
<array>
<xsl:apply-templates/>
</array>
</xsl:template>
<xsl:template match="item">
<object name="{name}">
<xsl:value-of select="value"/>
</object>
</xsl:template>
<xsl:template match="array" mode="json">
<!-- array -->
<xsl:text>[
</xsl:text>
<xsl:apply-templates mode="json"/>
<xsl:text>
]</xsl:text>
</xsl:template>
<xsl:template match="object" mode="json">
<!-- object -->
<xsl:text> {"</xsl:text>
<xsl:value-of select="#name"/>
<xsl:text>" : </xsl:text>
<xsl:value-of select="."/>
<xsl:text>}</xsl:text>
<xsl:if test="position()!=last()">
<xsl:text>,
</xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
XML Output
<?xml version="1.0" encoding="UTF-8"?>
<array>
<object name="Alpha">101</object>
<object name="Bravo">2.25</object>
<object name="Charlie">33</object>
</array>
Output file "json.txt"
[
{"Alpha" : 101},
{"Bravo" : 2.25},
{"Charlie" : 33}
]

XSLT transformation xml List item with sub list

will appreciate your help. i have xml as following:
<ul>
<li>list item 1 </li>
<li>List Item 2
<ul>
<li>List item 2.1</li>
<li>List Item 2.2</li>
</ul>
</li>
<li>List Item 3 </li>
</ul>
The output should be as following:
<list>
<item>
<paragraph>list item 1 </paragraph>
</item>
<item>
<paragraph>List Item 2 </paragraph>
<list>
<item>
<paragraph>List<emphasis> item</emphasis> 2.1 </paragraph>
</item>
<item>
<paragraph>List Item 2.2 </paragraph>
</item>
</list>
</item>
<item>
<paragraph>List Item 3 </paragraph>
</item>
</list>
I am using xlst version 3.0 as following:
<xsl:template match="ul">
<xsl:choose>
<xsl:when test="name(..)='li'">
<xsl:text disable-output-escaping="yes"></paragraph></xsl:text>
<list>
<xsl:apply-templates/>
</list>
</xsl:when>
<xsl:otherwise>
<list>
<xsl:apply-templates/>
</list></xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="li">
<item>
<xsl:text disable-output-escaping="yes"><paragraph></xsl:text>
<xsl:apply-templates/>
<xsl:text disable-output-escaping="yes"></paragraph></xsl:text>
</item>
</xsl:template>
I am getting the output almost as i would like but with extra closing paragraph element (</paragraph>) as following:
<list>
<item><paragraph>list item 1 </paragraph></item>
<item><paragraph>List Item 2 </paragraph><list>
<item><paragraph>List item 2.1 </paragraph></item>
<item><paragraph>List Item 2.2 </paragraph></item>
</list>
</paragraph></item>
<item><paragraph>List Item 3 </paragraph></item>
</list>
You should avoid using disable-output-escaping in this way. It is really not good practise, as you have seen.
What you can do, for your given XML, is use a template that matches text() and then wrap the paragraph around that:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:output method="xml" indent="yes""/>
<xsl:strip-space elements="*" />
<xsl:template match="ul">
<list>
<xsl:apply-templates/>
</list>
</xsl:template>
<xsl:template match="li">
<item>
<xsl:apply-templates/>
</item>
</xsl:template>
<xsl:template match="text()">
<paragraph>
<xsl:value-of select="normalize-space()" />
</paragraph>
</xsl:template>
</xsl:stylesheet>
Alternatively, if you could have mark-up in your text, like <li>list <b>item 1</b> </li>, and you wanted output like <paragraph>list<b>item 1</b></paragraph>, then you could make use of xsl:for-each-group.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="ul">
<list>
<xsl:apply-templates/>
</list>
</xsl:template>
<xsl:template match="li">
<item>
<xsl:for-each-group select="node()" group-ending-with="ul">
<paragraph>
<xsl:apply-templates select="current-group()[not(self::ul)]" />
</paragraph>
<xsl:apply-templates select="current-group()[self::ul]" />
</xsl:for-each-group>
</item>
</xsl:template>
<xsl:template match="text()">
<xsl:value-of select="normalize-space()" />
</xsl:template>
</xsl:stylesheet>

Find nodes with the same couple of attributes with xslt and add a new node

I need to analyse the following XML input:
<LIST>
<PRODUCT TYPE="1" REP="0">
<SUB CAT="1.1" COUNT="2">
<ITEM NAME="OCC" BEGIN="0" ND="49">
</ITEM>
<ITEM NAME="OCC" BEGIN="0" END="49">
</ITEM>
</SUB>
</PRODUCT>
<PRODUCT TYPE="1" REP="1">
<SUB CAT="1.1" COUNT="1">
<ITEM NAME="PRC" BEGIN="0" END="49">
</ITEM>
</SUB>
</PRODUCT>
</LIST>
and transform it with Xslt to obtain the following result:
<LIST>
<PRODUCT TYPE="1" REP="0">
<SUB CAT="1.1" COUNT="2">
<MULTIPLE />
<ITEM NAME="OCC" BEGIN="0" END="49">
</ITEM>
<MULTIPLE />
<ITEM NAME="OCC" BEGIN="0" END="49">
</ITEM>
</SUB>
</PRODUCT>
<PRODUCT TYPE="1" REP="1">
<SUB CAT="1.1" COUNT="1">
<MULTIPLE />
<ITEM NAME="PRC" BEGIN="0" END="49">
</ITEM>
</SUB>
</PRODUCT>
</LIST>
What I need to do is to check that the BEGIN and END of the ITEMS in two different PRODUCT node are the same, and if this is the case add the MULTIPLE node as a flag.
Any idea on how to proceed?
This is how I thought it could work:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="//PRODUCT[#TYPE='1']/SUB[#CAT='1.1']/ITEM">
<xsl:if test="//PRODUCT[#TYPE='1']/SUB[#CAT='1.1']/ITEM /RULE (#BEGIN <= current()/#BEGIN) and (#END >= current()/#END)]">
<xsl:element name="MULTIPLE">
</xsl:element>
</xsl:if>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
This can be achieved by means of a key to look up ITEM elements
<xsl:key name="item" match="ITEM" use="concat(#BEGIN, '|', #END)" />
Then, you just need a template that matches ITEM elements where there is at least 2 items in the key
<xsl:template match="ITEM[key('item', concat(#BEGIN, '|', #END))[2]]">
Using this in conjunction with the XSLT identity transform, gives you this XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="item" match="ITEM" use="concat(#BEGIN, '|', #END)" />
<xsl:template match="#*|node()" name="identity">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ITEM[key('item', concat(#BEGIN, '|', #END))[2]]">
<MULTIPLE />
<xsl:call-template name="identity" />
</xsl:template>
</xsl:stylesheet>
If you wish to restrict it to look for matches in the same product and sub-category, change the key to this...
<xsl:key name="item" match="ITEM" use="concat(../../#TYPE, '|', ../#CAT, '|', #BEGIN, '|', #END)" />
And adjust the template match accordingly....
<xsl:template match="ITEM[key('item', concat(../../#TYPE, '|', ../#CAT, '|', #BEGIN, '|', #END))[2]]">
You can try like this way by match the ITEM context:
<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="ITEM">
<xsl:if test="(
(#BEGIN = ancestor::PRODUCT/following-sibling::PRODUCT/descendant::ITEM/#BEGIN) and
(#END = ancestor::PRODUCT/following-sibling::PRODUCT/descendant::ITEM/#END))
or (
(#BEGIN = ancestor::PRODUCT/preceding-sibling::PRODUCT/descendant::ITEM/#BEGIN) and
(#END = ancestor::PRODUCT/preceding-sibling::PRODUCT/descendant::ITEM/#END)
)
">
<xsl:element name="MULTIPLE"/>
</xsl:if>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>

Unnest parent nodes by child node

In a XML there are items with 0-n attributes and an item should be copied for each attribute as a new item but with only one attribute.
Given is a XML like this:
<?xml version="1.0" encoding="utf-8" ?>
<items>
<item>
<name>A</name>
<attributes>
<attribute>
<key>attribute1</key>
<value>1</value>
</attribute>
</attributes>
</item>
<item>
<name>B</name>
</item>
<item>
<name>C</name>
<attributes>
<attribute>
<key>attribute1</key>
<value>5</value>
</attribute>
<attribute>
<key>attribute2</key>
<value>2</value>
</attribute>
<attribute>
<key>attribute3</key>
<value>1</value>
</attribute>
</attributes>
</item>
</items>
Result should be:
<?xml version="1.0" encoding="utf-8" ?>
<root>
<item>
<name>A</name>
<attribute_key>attribute1</attribute_key>
<attribute_value>1</attribute_value>
</item>
<item>
<name>B</name>
</item>
<item>
<name>C</name>
<attribute_key>attribute1</attribute_key>
<attribute_value>5</attribute_value>
</item>
<item>
<name>C</name>
<attribute_key>attribute2</attribute_key>
<attribute_value>2</attribute_value>
</item>
<item>
<name>C</name>
<attribute_key>attribute3</attribute_key>
<attribute_value>1</attribute_value>
</item>
</root>
What I have s far:
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="item">
<xsl:if test="not(attributes/attribute)">
<item>
<xsl:apply-templates select="#* | node()"/>
</item>
</xsl:if>
<xsl:for-each select="./attributes/attribute">
<xsl:copy-of select="ancestor::item"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
So, the parent "item" node gets copied correctly but how do I remove all attributes but the attribute from the for-each and place that attribute as a direct child of "item"?
Instead of copying the item node in its entirity, manually create a new item and copy only its children (apart from attributes)
<xsl:for-each select="attributes/attribute">
<item>
<xsl:copy-of select="ancestor::item/*[not(self::attributes)]"/>
<!-- Process attributes here -->
</item>
</xsl:for-each>
Processing the attributes is then just a matter of processing the children, and using xsl:element to create new ones
<xsl:for-each select="*">
<xsl:element name="attribute_{local-name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:for-each>
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 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="item">
<xsl:if test="not(attributes/attribute)">
<item>
<xsl:apply-templates select="#* | node()"/>
</item>
</xsl:if>
<xsl:for-each select="attributes/attribute">
<item>
<xsl:copy-of select="ancestor::item/*[not(self::attributes)]"/>
<xsl:for-each select="*">
<xsl:element name="attribute_{local-name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:for-each>
</item>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Or if you wanted to take a more template-based approach, this should also work
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 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="item[attributes/attribute]">
<xsl:apply-templates select="attributes/attribute" />
</xsl:template>
<xsl:template match="item">
<item>
<xsl:apply-templates select="#* | node()"/>
</item>
</xsl:template>
<xsl:template match="attribute">
<item>
<xsl:copy-of select="ancestor::item/*[not(self::attributes)]"/>
<xsl:apply-templates select="*" />
</item>
</xsl:template>
<xsl:template match="attribute/*">
<xsl:element name="attribute_{local-name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Have a look at following script that will explain you want are you looking for:
<?xml version="1.0" encoding="utf-8" ?>
<xsl:template match="/">
<root>
<xsl:apply-templates select="items/item" />
</root>
</xsl:template>
<xsl:template match="item">
<xsl:if test="not(attributes/attribute)">
<xsl:copy-of select="."></xsl:copy-of>
</xsl:if>
<xsl:apply-templates select="attributes/attribute">
<!-- sending value of name tag to a template matching attribute tag -->
<xsl:with-param name="name" select="name"></xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<!--Another template for attribute tag that will help individually tracking of attribute -->
<xsl:template match="attribute">
<!-- Taking value of name tag -->
<xsl:param name="name"></xsl:param>
<item>
<name>
<xsl:value-of select="$name" />
</name>
<attribute_key>
<xsl:value-of select="key" />
</attribute_key>
<attribute_value>
<xsl:value-of select="value" />
</attribute_value>
</item>
</xsl:template>

Transform to nest items within one list when source contains flat tagging

I have the following XML file:
<li id="s9781452281988.n39.i34"><i>See also</i>
<a class="term-ref" id="s9781452281988.n39.i6525" href="#s9781452281988.n39.i1899">Emotion</a>;
<a class="term-ref" id="s9781452281988.n39.i6526" href="#s9781452281988.n39.i3312">Interpersonal conflict</a></li>
And I want the output to be the following:
<item>See also
<list rend="runon">
<item><term>Emotion</term></item>
<item><term>Interpersonal conflict</term></item>
</list>
</item>
Basically if I have multiple a[#class='term-ref'], the first instance should start the list rend="runon" and subsequent a[#class='term-ref'] should be included as item/term within the list.
The below was my try, but it is not working as I had hoped, and is closing the list before the second item/term (elements which are also not being output):
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
version="2.0">
<xsl:template match="li">
<xsl:element name="item">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="a[#class='term-ref'][1]">
<xsl:element name="list">
<xsl:attribute name="rend" select="'runon'"/>
<xsl:element name="item">
<xsl:element name="term">
<xsl:apply-templates/>
</xsl:element>
</xsl:element>
<xsl:if test="a[#class='term-ref'][position() >1]">
<xsl:element name="item">
<xsl:element name="term">
<xsl:apply-templates/>
</xsl:element>
</xsl:element>
</xsl:if>
</xsl:element>
</xsl:template>
<xsl:template match="li//text()">
<xsl:value-of select="translate(., '.,;', '')"/>
</xsl:template>
</xsl:stylesheet>
On the source, XML, the above stylesheet produces this output:
<item>See also
<list rend="runon">
<item><term>Emotion</term></item>
</list>
Interpersonal conflict</item>
Which is incorrect.
What am i doing wrong?
This short transformation (almost completely "push style", with no conditional instructions, no xsl:element and no unnecessary function calls like translate() or replace()):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="li">
<item><xsl:apply-templates/></item>
</xsl:template>
<xsl:template match="a[#class='term-ref'][1]">
<list rend="runon">
<xsl:apply-templates mode="group"
select="../a[#class='term-ref']"/>
</list>
</xsl:template>
<xsl:template match="a[#class='term-ref']" mode="group">
<item><term><xsl:apply-templates/></term></item>
</xsl:template>
<xsl:template match="a[#class='term-ref']|li/text()" priority="-1"/>
</xsl:stylesheet>
when applied on the provided XML document -- which is well-formed:
<li id="s9781452281988.n39.i34"><i>See also</i>
<a class="term-ref" id="s9781452281988.n39.i6525"
href="#s9781452281988.n39.i1899">Emotion</a>;
<a class="term-ref" id="s9781452281988.n39.i6526"
href="#s9781452281988.n39.i3312">Interpersonal conflict.</a>.
</li>
produces the wanted, correct result:
<item>See also<list rend="runon">
<item>
<term>Emotion</term>
</item>
<item>
<term>Interpersonal conflict.</term>
</item>
</list>
</item>
This should work...
XML Input (well-formed)
<doc>
<li id="s9781452281988.n39.i34"><i>See also</i>
<a class="term-ref" id="s9781452281988.n39.i6525" href="#s9781452281988.n39.i1899">Emotion</a>;
<a class="term-ref" id="s9781452281988.n39.i6526" href="#s9781452281988.n39.i3312">Interpersonal conflict.</a>.
</li>
</doc>
XSLT 2.0
<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="li">
<item>
<xsl:apply-templates select="i/text()"/>
<xsl:if test="a">
<list rend="runon">
<xsl:apply-templates select="a"/>
</list>
</xsl:if>
</item>
</xsl:template>
<xsl:template match="a">
<item><term><xsl:apply-templates select="node()"/></term></item>
</xsl:template>
<xsl:template match="li//text()">
<xsl:value-of select="replace(.,'[.,;]','')"/>
</xsl:template>
</xsl:stylesheet>
Output
<doc>
<item>See also<list rend="runon">
<item>
<term>Emotion</term>
</item>
<item>
<term>Interpersonal conflict</term>
</item>
</list>
</item>
</doc>
This should do what you are looking to do:
<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:template match="li">
<xsl:element name="item">
<xsl:apply-templates select="node()" />
<xsl:apply-templates select="." mode="items" />
</xsl:element>
</xsl:template>
<xsl:template match="li//text()">
<xsl:value-of select="normalize-space(translate(., '.,;', ''))"/>
</xsl:template>
<xsl:template match="a[#class = 'term-ref']" />
<xsl:template match="node()" mode="items" />
<xsl:template match="li" mode="items">
<xsl:apply-templates mode="items" />
</xsl:template>
<xsl:template match="li[count(a[#class = 'term-ref']) > 1]" mode="items">
<list rend="runon">
<xsl:apply-templates select="a[#class = 'term-ref']" mode="items" />
</list>
</xsl:template>
<xsl:template match="a[#class = 'term-ref']" mode="items">
<item>
<term>
<xsl:value-of select="."/>
</term>
</item>
</xsl:template>
</xsl:stylesheet>
When run on your sample input, this produces:
<item>
See also<list rend="runon">
<item>
<term>Emotion</term>
</item>
<item>
<term>Interpersonal conflict</term>
</item>
</list>
</item>
When run on an input file with just one a.term-ref, this produces:
<item>
See also<item>
<term>Interpersonal conflict</term>
</item>
</item>