JanineS: XSL | Satz in einzelne Wörter zerlegen

Hallo zusammen,

ich habe ein Glossar mit verschiedenen Begriffen und Beschreibungen. Innerhalb der Beschreibungen kommen wieder einige definierte Begriffe vor, die ich gerne verlinken möchte. Dazu muss ich aber erstmal herausfinden, welche Begriffe aus den Beschreibungen als Begriff im Glossar definiert sind.

Ich habe also beispielsweise die folgende Quelldatei:

<?xml version="1.0" encoding="ISO-8859-1"?>
<definitions xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
	<def>
		<term>Abhilfemaßnahme (FDA)</term>
		<descr>Routine-Instandhaltung oder Wartung eines Gerätes etc.</descr>
	</def>
	<def>
		<term>Routine</term>
		<descr>Hier taucht der Begriff Abhilfemaßnahme (FDA) auf.</descr>
	</def>
	<def>
		<term>Gerät</term>
		<descr>Dies ist ein Test für Routine und Abhilfemaßnahme (FDA).</descr>
	</def>
</definitions>

Ich möchte nun also zunächst (die Schleife bastel ich später drumrum) innerhalb der ersten Beschreibung die Begriffe "Routine-Instandhaltung" und "Gerätes" erkennen, da "Routine" und "Gerät" definierte Begriffe sind.

Jetzt dachte ich, ich zerlege zuerst mit regex meinen descr-String in einzelne Begriffe und prüfe dann mit einer Schleife, ob diese Bestandteil von def/term sind. Das klappt auch soweit ganz gut, nur mit dem Bindestrich zwischen Routine-Instandhaltung kommt mein Code nicht so gut klar. Habt ihr eine Ahnung, was ich falsch gemacht habe? Oder gibt es einfachere/bessere Lösungen, die mich zum Ziel führen werden?

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions" version="2.0" exclude-result-prefixes="#all">
	<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
	<xsl:template match="definitions">
		<xsl:variable name="term" select="def/term"/>
		<xsl:variable name="actdef" select="def[1]"/>
		<xsl:variable name="actdefdescr" select="fn:concat(' ', $actdef/descr, ' ')"/>
		<xsl:variable name="actdescr">
			<xsl:analyze-string select="$actdefdescr" regex="( )?([A-ZÄÖÜ]*[a-zäöü]*[0-9]*[-,.;]*)( )">
				<xsl:matching-substring>
					<txt><xsl:value-of select="regex-group(2)"/></txt>
				</xsl:matching-substring>
				<xsl:non-matching-substring>
					<xsl:value-of select="$actdef/descr"/>
				</xsl:non-matching-substring>
			</xsl:analyze-string>
		</xsl:variable>
		<def>
			<xsl:attribute name="actterm" select="$actdef/term"/>
			<xsl:attribute name="actdescr" select="$actdef/descr"/>
			<link>
				<xsl:for-each select="$actdescr/txt">
				<txt><xsl:value-of select="."/></txt>
				</xsl:for-each>
			</link>
		</def>
	</xsl:template>
</xsl:stylesheet>

Meine bisherige Ausgabe sieht so aus. Ich habe unter @actterm und @actdescr noch einmal die Quelle angegeben, damit ich mein Ergebnis leichter überprüfen kann. Darunter sollten alle einzelnen Wörter gelistet werden. Diese Liste würde ich dann in einem späteren Schritt abgleichen mit der Liste meiner Glossarbegriffe, die ich einfach in einer Variable speichere.

<?xml version="1.0" encoding="UTF-8"?>
<def actterm="Abhilfemaßnahme (FDA)" actdescr="Routine-Instandhaltung oder Wartung eines Gerätes etc.">
	<link>
		<txt></txt>
		<txt>Instandhaltung</txt>
		<txt>oder</txt>
		<txt>Wartung</txt>
		<txt>eines</txt>
		<txt>Gerätes</txt>
		<txt>etc.</txt>
	</link>
</def>

Viele Grüße und danke schonmal, Janine

akzeptierte Antworten

  1. Hallo Janine,

    Jetzt dachte ich, ich zerlege zuerst mit regex meinen descr-String in einzelne Begriffe und prüfe dann mit einer Schleife, ob diese Bestandteil von def/term sind. Das klappt auch soweit ganz gut, nur mit dem Bindestrich zwischen Routine-Instandhaltung kommt mein Code nicht so gut klar. Habt ihr eine Ahnung, was ich falsch gemacht habe? Oder gibt es einfachere/bessere Lösungen, die mich zum Ziel führen werden?

    Probiere es zunächst so:

    <xsl:template match="definitions">
      <xsl:variable name="term" select="def/term"/>
      <xsl:variable name="actdef" select="def[1]"/>
      <xsl:variable name="actdefdescr" select="fn:tokenize($actdef/descr, ' ')"/>
      <def>
        <link>
          <xsl:for-each select="$actdefdescr">
            <txt><xsl:value-of select="."/></txt>
          </xsl:for-each>
        </link>
      </def>
    </xsl:template>
    

    mit diesem Ergebnis:

    <?xml version="1.0" encoding="UTF-8"?>
    <def>
       <link>
          <txt>Routine-Instandhaltung</txt>
          <txt>oder</txt>
          <txt>Wartung</txt>
          <txt>eines</txt>
          <txt>Gerätes</txt>
          <txt>etc.</txt>
       </link>
    </def>
    

    Falls auch die Teilbegriffe aus Routine-Instandhaltung benötigt werden, kann man wiederum mit der tokenize-Funktion über den Bindestrich gehen.

    Grüße,
    Thomas

    1. Nachtrag:

      Falls auch die Teilbegriffe aus Routine-Instandhaltung benötigt werden, kann man wiederum mit der tokenize-Funktion über den Bindestrich gehen.

      fn:tokenize($actdef/descr, ' |-') oder ggf. zur Behandlung von weiterem Leerraum fn:tokenize($actdef/descr, '\s|-')

      erzeugt:

      <?xml version="1.0" encoding="UTF-8"?>
      <def>
         <link>
            <txt>Routine</txt>
            <txt>Instandhaltung</txt>
            <txt>oder</txt>
            <txt>Wartung</txt>
            <txt>eines</txt>
            <txt>Gerätes</txt>
            <txt>etc.</txt>
         </link>
      </def>
      

      Grüße,
      Thomas

      1. Hallo Thomas,

        vielen Dank für deine schnelle Hilfe! Der Code funktioniert wunderbar und ich habe mal wieder ein neues XSL-Element gelernt. Ich werde sehen, wie weit ich mit meinem Glossar komme. Notfalls melde ich mich wieder 😉

        Viele Grüße und bleibt gesund! Janine

        1. Notfalls melde ich mich wieder 😉

          So schnell kann es gehen und ich bin wieder da... Ich merke nämlich gerade, dass mein eigentliches Problem immer noch nicht gelöst wurde. Hier mein erster Versuch, der ein ähnliches (wenn auch nicht so gutes) Ergebnis eingebracht hat:

          <?xml version="1.0" encoding="UTF-8"?>
          <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions" version="2.0" exclude-result-prefixes="#all">
          	<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
          	<xsl:template match="definitions">
          		<xsl:variable name="term" select="def/term"/>
          		<all>
          			<xsl:for-each select="//def">
          				<xsl:variable name="actdef" select="."/>
          				<def>
          					<xsl:for-each select="$term">
          						<xsl:variable name="actterm" select="$actdef/term"/>
          						<xsl:if test="fn:contains($actdef/descr, .)">
          							<link>
          									<xsl:value-of select="fn:substring-before($actdef/descr, .)"/><a><xsl:value-of select="."/></a><xsl:value-of select="fn:substring-after($actdef/descr, .)"/>
          							</link>
          						</xsl:if>
          					</xsl:for-each>
          				</def>
          			</xsl:for-each>
          		</all>
          	</xsl:template>
          </xsl:stylesheet>
          

          Alles, was ich will, ist, den Eingabestring "descr" auf Begriffe aus "term" zu prüfen und diese mit einem HTML-Link (<a>) zu markieren, damit ich von einem Glossarbegriff zum nächsten springen kann. Doch diese verflixten Schleifen schreiben mir nur jeweils einen Begriff um. Brauche ich dazu zwei Templates und muss den Code so oft durchführen und immer wieder die Ausgabe als neuen Eingabestring verwenden, bis ich keine offenen Begriffe mehr habe? Wie könnte das aussehen?

          Ich freue mich über jeden Hinweis! Janine

          1. Hallo Janine,

            Alles, was ich will, ist, den Eingabestring "descr" auf Begriffe aus "term" zu prüfen und diese mit einem HTML-Link (<a>) zu markieren, damit ich von einem Glossarbegriff zum nächsten springen kann. Doch diese verflixten Schleifen schreiben mir nur jeweils einen Begriff um. Brauche ich dazu zwei Templates und muss den Code so oft durchführen und immer wieder die Ausgabe als neuen Eingabestring verwenden, bis ich keine offenen Begriffe mehr habe? Wie könnte das aussehen?

            Gib nochmal das konkret gesuchte Ergebnis an. Das erspart evtl. unnötige Aktionen. Ich verstehe noch nicht die gewünschte Zuordnung von term- zu den jeweiligen descr-Inhalten.

            Grüße,
            Thomas

            1. Gib nochmal das konkret gesuchte Ergebnis an. Das erspart evtl. unnötige Aktionen. Ich verstehe noch nicht die gewünschte Zuordnung von term- zu den jeweiligen descr-Inhalten.

              Hallo Thomas,

              hier noch einmal meine Quelldatei:

              <?xml version="1.0" encoding="ISO-8859-1"?>
              <definitions xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
              	<def>
              		<term>Abhilfemaßnahme (FDA)</term>
              		<descr>Routine-Instandhaltung oder Wartung eines Gerätes etc.</descr>
              	</def>
              	<def>
              		<term>Routine</term>
              		<descr>Hier taucht der Begriff Abhilfemaßnahme (FDA) auf.</descr>
              	</def>
              	<def>
              		<term>Gerät</term>
              		<descr>Dies ist ein Test für Routine und Abhilfemaßnahme (FDA).</descr>
              	</def>
              </definitions>
              
              

              und meine gewünschte Zieldatei in der finalen Ausgabe:

              <?xml version="1.0" encoding="ISO-8859-1"?>
              <definitions xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
              	<def>
              		<term>Abhilfemaßnahme (FDA)</term>
              		<descr><a href="Routine">Routine</a>-Instandhaltung oder Wartung eines <a href="Gerät">Gerätes</a> etc.</descr>
              	</def>
              	<def>
              		<term>Routine</term>
              		<descr>Hier taucht der Begriff <a href="Abhilfemaßnahme (FDA)">Abhilfemaßnahme (FDA)</a> auf.</descr>
              	</def>
              	<def>
              		<term>Gerät</term>
              		<descr>Dies ist ein Test für <a href="Routine">Routine</a> und <a href="Abhilfemaßnahme (FDA)">Abhilfemaßnahme (FDA)</a>.</descr>
              	</def>
              </definitions>
              
              

              Viele Grüße, Janine

              1. Hallo Janine,

                hier ein Ansatz, der noch nicht ganz perfekt funktioniert und auf einzelne Textknoten fokussiert. Insofern wird "Abhilfemaßnahme (FDA)" (= zwei Textknoten) nicht verlinkt. Also ggf. noch weiter modifizieren.

                <?xml version="1.0" encoding="UTF-8"?>
                <xsl:stylesheet
                  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                  xmlns:fn="http://www.w3.org/2005/xpath-functions"
                  xmlns:xs="http://www.w3.org/2001/XMLSchema"
                  exclude-result-prefixes="#all"
                  version="2.0">
                
                	<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
                
                  <xsl:variable name="terms" select="//def/term"/>
                
                	<xsl:template match="definitions">
                		<definitions>		
                      <xsl:apply-templates select="def"/>
                		</definitions>
                	</xsl:template>
                
                	<xsl:template match="def">
                	  <def>
                	    <term><xsl:value-of select="term"/></term>
                	    <descr>
                      <xsl:apply-templates select="descr" mode="term">
                        <xsl:with-param name="terms" as="xs:string+" select="$terms"/>
                      </xsl:apply-templates>
                	    </descr>
                	  </def>
                	</xsl:template>
                
                  <xsl:template match="text()" mode="term">
                    <xsl:param name="terms" as="xs:string+"/>
                
                    <xsl:analyze-string select="." regex="{fn:string-join($terms, '|')}">
                      <xsl:matching-substring>
                        <a href="{.}"><xsl:value-of select="."/></a>
                      </xsl:matching-substring>
                      <xsl:non-matching-substring>
                        <xsl:value-of select="."/>
                      </xsl:non-matching-substring>
                    </xsl:analyze-string>
                  </xsl:template>
                
                </xsl:stylesheet>
                

                Ergebnis:

                <?xml version="1.0" encoding="UTF-8"?>
                <definitions>
                  <def>
                    <term>Abhilfemaßnahme (FDA)</term>
                    <descr><a href="Routine">Routine</a>-Instandhaltung oder Wartung eines <a href="Gerät">Gerät</a>es etc.</descr>
                  </def>
                  <def>
                    <term>Routine</term>
                    <descr>Hier taucht der Begriff Abhilfemaßnahme (FDA) auf.</descr>
                  </def>
                  <def>
                    <term>Gerät</term>
                    <descr>Dies ist ein Test für <a href="Routine">Routine</a> und Abhilfemaßnahme (FDA).</descr>
                  </def>
                </definitions>
                

                Grüße,
                Thomas

                1. Hallo Thomas,

                  besten Dank für deinen Entwurf. Ich verstehe dabei aber nicht ganz, welchen Unterschied xs:string+ zu xs:string hat und warum ich bei fn:string-join keinen anderen Operator nutzen kann (habe es auch mit Leerzeichen, Komma und Schrägstrich statt dem Union-Operator | probiert). Warum trennt er $terms durch den Union Operator nach Wörtern und nicht nach gespeicherten Elementen? Wenn ich die Elemente unter $terms zählen lasse, sind es genau 3 (also "Abhilfemaßnahme (FDA)" als ein Element).

                  Ich habe ein wenig mit deiner Lösung herumgespielt und versucht, eine Schleife um $terms bzw. um xsl:analyze-string zu legen, aber so richtig ist mir nichts geglückt.

                  Mein Quellcode ist etwas komplexer als ich ihn hier dargestellt habe (u.a. mit mehreren Kindelemente, zweisprachig und v.a. viele Wortgruppen innerhalb von <term>. Deshalb muss ich mir noch etwas anderes einfallen lassen...

                  Dennoch vielen lieben Dank für deine Hilfe und guten Ideen! Janine

                  1. Hallo Janine,

                    besten Dank für deinen Entwurf. Ich verstehe dabei aber nicht ganz, welchen Unterschied xs:string+ zu xs:string hat und warum ich bei fn:string-join keinen anderen Operator nutzen kann (habe es auch mit Leerzeichen, Komma und Schrägstrich statt dem Union-Operator | probiert). Warum trennt er $terms durch den Union Operator nach Wörtern und nicht nach gespeicherten Elementen? Wenn ich die Elemente unter $terms zählen lasse, sind es genau 3 (also "Abhilfemaßnahme (FDA)" als ein Element).

                    xs:string ist genau ein String. xs:string+ steht für 1 bis n Strings in einer Sequenz wie ('a', 'b', 'c'). Das + entspricht dem in der DTD- oder Regex-Syntax.

                    Der | bildet den ODER-Operator für den regulären Ausdruck mit den einzelnen Begriffen.

                    Klar, 3, denn "Abhilfemaßnahme (FDA)" war doch offenbar auch als ein Begriff gedacht, siehe die erwartete Ausgabestruktur.

                    Ich habe ein wenig mit deiner Lösung herumgespielt und versucht, eine Schleife um $terms bzw. um xsl:analyze-string zu legen, aber so richtig ist mir nichts geglückt.

                    Ja, ist ziemlich vertrackt. Mit separaten Begriffen funktioniert es gut, mit Wortkombinationen wird es komplexer.

                    Grüße,
                    Thomas

                    1. So, jetzt habe ich nochmal weitergebastelt.

                      Folgender Quellcode:

                      <?xml version="1.0" encoding="ISO-8859-1"?>
                      <definitions xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
                      	<def>
                      		<term>Abhilfemaßnahme (FDA)</term>
                      		<descr>Routine-Instandhaltung oder Wartung eines Gerätes etc.</descr>
                      	</def>
                      	<def>
                      		<term>Routine</term>
                      		<descr>Hier taucht der Begriff Abhilfemaßnahme (FDA) auf.</descr>
                      	</def>
                      	<def>
                      		<term>Gerät</term>
                      		<descr>Dies ist ein Test für Routine und Abhilfemaßnahme (FDA).</descr>
                      	</def>
                      </definitions>
                      
                      

                      Mit diesem ersten Stylesheet:

                      <?xml version="1.0" encoding="UTF-8"?>
                      <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions" version="2.0" exclude-result-prefixes="#all">
                      	<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
                      	<xsl:template match="definitions">
                      		<xsl:variable name="term" select="def/term"/>
                      		<all>
                      			<xsl:for-each select="//def">
                      				<xsl:variable name="actdef" select="."/>
                      				<def>
                      					<xsl:attribute name="actterm" select="$actdef/term"/>
                      					<xsl:attribute name="actdescr" select="$actdef/descr"/>
                      					<terms>
                      						<xsl:for-each select="$term">
                      							<xsl:variable name="actterm" select="$actdef/term"/>
                      							<xsl:if test="fn:contains($actdef/descr, .)">
                      								<term>
                      									<xsl:value-of select="fn:substring-before($actdef/descr, .)"/>
                      									<xsl:text>&lt;a&gt;</xsl:text>
                      										<xsl:value-of select="."/>
                      									<xsl:text>&lt;/a&gt;</xsl:text>
                      									<xsl:value-of select="fn:substring-after($actdef/descr, .)"/>
                      								</term>
                      							</xsl:if>
                      						</xsl:for-each>
                      					</terms>
                      				</def>
                      			</xsl:for-each>
                      		</all>
                      	</xsl:template>
                      </xsl:stylesheet>
                      
                      

                      Erhalte ich die folgende Ausgabe (hier werden die Begriffe noch doppelt geschrieben):

                      <?xml version="1.0" encoding="UTF-8"?>
                      <all>
                      	<def actterm="Abhilfemaßnahme (FDA)" actdescr="Routine-Instandhaltung oder Wartung eines Gerätes etc.">
                      		<terms>
                      			<term>&lt;a&gt;Routine&lt;/a&gt;-Instandhaltung oder Wartung eines Gerätes etc.</term>
                      			<term>Routine-Instandhaltung oder Wartung eines &lt;a&gt;Gerät&lt;/a&gt;es etc.</term>
                      		</terms>
                      	</def>
                      	<def actterm="Routine" actdescr="Hier taucht der Begriff Abhilfemaßnahme (FDA) auf.">
                      		<terms>
                      			<term>Hier taucht der Begriff &lt;a&gt;Abhilfemaßnahme (FDA)&lt;/a&gt; auf.</term>
                      		</terms>
                      	</def>
                      	<def actterm="Gerät" actdescr="Dies ist ein Test für Routine und Abhilfemaßnahme (FDA).">
                      		<terms>
                      			<term>Dies ist ein Test für Routine und &lt;a&gt;Abhilfemaßnahme (FDA)&lt;/a&gt;.</term>
                      			<term>Dies ist ein Test für &lt;a&gt;Routine&lt;/a&gt; und Abhilfemaßnahme (FDA).</term>
                      		</terms>
                      	</def>
                      </all>
                      

                      Und auf diese Ausgabe wende ich nun das 2. Stylesheet an (geht das auch in einem Schritt?):

                      <?xml version="1.0" encoding="UTF-8"?>
                      <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fn="http://www.w3.org/2005/xpath-functions" version="2.0" exclude-result-prefixes="#all">
                      	<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
                      	<xsl:template match="all">
                      		<all>
                      			<xsl:for-each select="def">
                      				<def>
                      					<xsl:attribute name="actterm" select="@actterm"/>
                      					<xsl:attribute name="actdescr" select="@actdescr"/>
                      					<descr>
                      						<xsl:choose>
                      							<xsl:when test="fn:count(terms/term) > 1"><!--Für alle Elemente mit mehr als einer Beschreibung...-->
                      								<xsl:variable name="text" select=""/><!--... soll zunächst eine leere Variable angelegt werden.-->
                      								<xsl:for-each select="terms/term">
                      									<xsl:variable name="text">
                      										<xsl:value-of select="$text"/><!--Die leere Variable wird hier aufgerufen und der neue Wert in die gleiche Variable gespeichert.-->
                      										<xsl:copy-of select="fn:substring-before(., '&lt;/a&gt;')"/>
                      										<xsl:text>&lt;/a&gt;</xsl:text>
                      									</xsl:variable>
                      								</xsl:for-each>
                      								<xsl:value-of select="$text"/><!--Hier wird meine zusammengesetzte Variable ausgegeben.-->
                      							</xsl:when>
                      							<xsl:otherwise>
                      								<xsl:copy-of select="terms/term"/>
                      							</xsl:otherwise>
                      						</xsl:choose>
                      					</descr>
                      				</def>
                      			</xsl:for-each>
                      		</all>
                      	</xsl:template>
                      </xsl:stylesheet>
                      
                      

                      Leider funktioniert das hier noch nicht so wie es soll. Ohne <xsl:for-each select="terms/term"> klappt es: also nur durch Ausgabe von <xsl:copy-of select="fn:substring-before(terms/term[1], '&lt;/a&gt;')"/> Es ist auch noch ein Fehler bei der Definition der leeren Variablen, den ich nicht verstehe.

                      Siehst du hier meinen Fehler?

                      Ziel sollte etwas in dieser Art sein:

                      <?xml version="1.0" encoding="UTF-8"?>
                      <all>
                      	<def actterm="Abhilfemaßnahme (FDA)" actdescr="Routine-Instandhaltung oder Wartung eines Gerätes etc.">
                      		<descr>&lt;a&gt;Routine&lt;/a&gt;-Instandhaltung oder Wartung eines &lt;a&gt;Gerät&lt;/a&gt;es etc.</descr>
                      	</def>
                      	<def actterm="Routine" actdescr="Hier taucht der Begriff Abhilfemaßnahme (FDA) auf.">
                      		<descr>Hier taucht der Begriff &lt;a&gt;Abhilfemaßnahme (FDA)&lt;/a&gt; auf.</descr>
                      	</def>
                      	<def actterm="Gerät" actdescr="Dies ist ein Test für Routine und Abhilfemaßnahme (FDA).">
                      		<descr>Dies ist ein Test für &lt;a&gt;Routine&lt;/a&gt; und &lt;a&gt;Abhilfemaßnahme (FDA)&lt;/a&gt;.</descr>
                      	</def>
                      </all>
                      

                      Viele Grüße, Janine

                      1. Hallo Janine,

                        Ziel sollte etwas in dieser Art sein:

                        <?xml version="1.0" encoding="UTF-8"?>
                        <all>
                        	<def actterm="Abhilfemaßnahme (FDA)" actdescr="Routine-Instandhaltung oder Wartung eines Gerätes etc.">
                        		<descr>&lt;a&gt;Routine&lt;/a&gt;-Instandhaltung oder Wartung eines &lt;a&gt;Gerät&lt;/a&gt;es etc.</descr>
                        	</def>
                        	<def actterm="Routine" actdescr="Hier taucht der Begriff Abhilfemaßnahme (FDA) auf.">
                        		<descr>Hier taucht der Begriff &lt;a&gt;Abhilfemaßnahme (FDA)&lt;/a&gt; auf.</descr>
                        	</def>
                        	<def actterm="Gerät" actdescr="Dies ist ein Test für Routine und Abhilfemaßnahme (FDA).">
                        		<descr>Dies ist ein Test für &lt;a&gt;Routine&lt;/a&gt; und &lt;a&gt;Abhilfemaßnahme (FDA)&lt;/a&gt;.</descr>
                        	</def>
                        </all>
                        

                        Das habe ich unter Rückgriff auf zwei Funktionen von FunctX erreicht [functx:replace-multi und functx:if-absent]. Ist schon eine ziemlich wilde Konstruktion geworden, vor allem um den Part mit (FDA) richtig einzubeziehen, dafür die replace-Aktionen. Die runden Klammern sind ja selbst Teile von regulären Ausdrücken zur Gruppierung.

                        <?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"
                          xmlns:fn="http://www.w3.org/2005/xpath-functions"
                          xmlns:functx="http://www.functx.com"
                          exclude-result-prefixes="#all">
                        
                          <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
                        
                          <xsl:function name="functx:replace-multi" as="xs:string?">
                            <xsl:param name="arg" as="xs:string?"/>
                            <xsl:param name="changeFrom" as="xs:string*"/>
                            <xsl:param name="changeTo" as="xs:string*"/>
                        
                            <xsl:sequence select="if(fn:count($changeFrom) > 0) then
                              functx:replace-multi(replace($arg, $changeFrom[1],
                              functx:if-absent($changeTo[1], '')), $changeFrom[fn:position() > 1],
                              $changeTo[fn:position() > 1]) else $arg"/>
                          </xsl:function>
                        
                          <xsl:function name="functx:if-absent" as="item()*">
                            <xsl:param name="arg" as="item()*"/>
                            <xsl:param name="value" as="item()*"/>
                        
                            <xsl:sequence select="if(fn:exists($arg)) then $arg else $value"/>
                          </xsl:function>
                        
                          <xsl:template match="definitions">
                            <xsl:variable name="from" select="for $s in //def/term return
                              fn:replace(fn:replace($s, ' \(', '_'), '\)', '~')" as="xs:string*"/>
                        
                            <xsl:variable name="to" select="for $s in $from return
                              fn:concat('&lt;a&gt;', fn:replace(fn:replace($s, ' \(', '_'), '\)', '~'),
                              '&lt;/a&gt;')" as="xs:string*"/>
                        
                            <all>
                              <xsl:for-each select="def">
                                <def actterm="{term}" actdescr="{descr}">
                                  <descr>
                                    <xsl:value-of select="fn:replace(fn:replace(
                                      functx:replace-multi(fn:replace(fn:replace(descr, ' \(', '_'), '\)', '~'),
                                      $from, $to), '_', ' ('), '~', ')')"/>
                                  </descr>
                                </def>
                              </xsl:for-each>
                            </all>
                          </xsl:template>
                        
                        </xsl:stylesheet>
                        

                        Viel Spaß damit. 😉

                        Grüße,
                        Thomas