There is a general solution that does not need to know what all the final punctuation marks are :
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output omit-xml-declaration="yes" indent="yes"/> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="text()[matches(., '^.*\p{P}$')]"> <xsl:sequence select="replace(., '(^.*)\p{P}$', '$1')"/> </xsl:template> </xsl:stylesheet>
When this conversion is applied to this XML document :
<x> <t>Some text .</t> <t>Some text2 ;</t> <t>Some text3 (</t> <t>Some text4 !</t> <t>Some text5 "</t> </x>
the desired, correct result is output:
<x> <t>Some text </t> <t>Some text2 </t> <t>Some text3 </t> <t>Some text4 </t> <t>Some text5 </t> </x>
Explanation
Proper use of the character class / category p{P} .
\p is an escape for the punctuation category. P is all the punctuation property.
Update
The OP provided a specific XML source document and its conversion code.
Here is her code modified using the above solution :
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" xmlns:marc="http://www.loc.gov/MARC21/slim" > <xsl:output method="xml" encoding="UTF-8" indent="yes"/> <xsl:template match="/"> <xsl:for-each select="marc:collection/marc:record"> <xsl:result-document method="xml" href="banana_{marc:controlfield[@tag=001]}.xml"> <metadata> <xsl:apply-templates select="self::marc:record"/> </metadata> </xsl:result-document> </xsl:for-each> </xsl:template> <xsl:template match="marc:record"> <pubinfo> <xsl:variable name="vSub1" select="marc:datafield[@tag=260]/marc:subfield[@code='a']"/> <xsl:variable name="vSub2" select="marc:datafield[@tag=260]/marc:subfield[@code='b']"/> <pubplace><xsl:value-of select="replace($vSub1, '(^.*)\s\p{P}$', '$1')"/></pubplace> <publish><xsl:value-of select="replace($vSub2, '(^.*)\s\p{P}$', '$1')" /></publish> </pubinfo> </xsl:template> </xsl:stylesheet>
source share