JanineS: XSL | String vor und nach Code auslesen

Guten Abend zusammen,

ich hänge mal wieder an einem kleinen XSL-Problem und habe folgenden Ausgangscode:

<?xml version="1.0" encoding="UTF-8"?>
<mycode>
   <text>
      <de>Hier steht mein Text mit einer <a href="Link1">Verlinkung</a> drin.</de>
      <en>Here is my text with a <a href="Link1">link</a> in it.</en>
   </text>
   <text>
      <de>Hier steht mein Text mit einer <a href="Link1">Verlinkung</a> und noch einem zweiten <a href="Link2">Link</a> drin.</de>
      <en>Here is my text with a <a href="Link1">link</a> and a second <a href="Link2">link</a> in it.</en>
    </text>
</mycode>

Ausgabe soll sein:

<?xml version="1.0" encoding="UTF-8"?>
<mycode>
   <text>
      <de>Hier steht mein Text mit einer <a href="Link1_de.html">Verlinkung</a> drin.</de>
      <en>Here is my text with a <a href="Link1_en.html">link</a> in it.</en>
   </text>
   <text>
      <de>Hier steht mein Text mit einer <a href="Link1_de.html">Verlinkung</a> und noch einem zweiten <a href="Link2_de.html">Link</a> drin.</de>
      <en>Here is my text with a <a href="Link1_en.html">link</a> and a second <a href="Link2_en.html">link</a> in it.</en>
    </text>
</mycode>

Leider bin ich an diesem XSLT mal wieder über der Schleife abgestorben... Irgendwie immer, das Konstrukt will einfach nicht in meinen Kopf rein :( Habt ihr Vorschläge? Gerne nehme ich auch Hinweise zur Codeoptimierung an. Sollte ich bei der Verlinkung mit einem zweiten Template arbeiten, um den Code für de und en nicht doppelt schreiben zu müssen? Dann muss ich allerdings beim Parameter xsl:copy-of verwenden, anstatt xsl:value-of, damit der gesamte Knoten <de> samt Kindknoten übernommen wird, oder?

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:fn="http://www.w3.org/2005/xpath-functions" exclude-result-prefixes="#all" version="2.0">
     <xsl:output method="xml" encoding="UTF-8"/>
     <xsl:template match="/">
          <mycode>
               <xsl:for-each select="/mycode/text">
                    <text>
                         <de>
                              <xsl:choose>
                                   <xsl:when test="de/a">
                                        <xsl:value-of select="de/text()[1]"/>
                                        <a>
                                             <xsl:attribute select="fn:concat(de/a[1]/@href, '_de.html')" name="href"/>
                                             <xsl:value-of select="de/a[1]/text()"/>
                                        </a>
                                        <xsl:value-of select="de/text()[2]"/>
                                   </xsl:when>
                                   <xsl:otherwise>
                                        <xsl:value-of select="de"/>
                                   </xsl:otherwise>
                              </xsl:choose>
                         </de>
                         <en>
                              <xsl:choose>
                                   <xsl:when test="en/a">
                                        <xsl:value-of select="en/text()[1]"/>
                                        <a>
                                             <xsl:attribute select="fn:concat(en/a[1]/@href, '_en.html')" name="href"/>
                                             <xsl:value-of select="en/a[1]/text()"/>
                                        </a>
                                        <xsl:value-of select="en/text()[2]"/>
                                   </xsl:when>
                                   <xsl:otherwise>
                                        <xsl:value-of select="en"/>
                                   </xsl:otherwise>
                              </xsl:choose>
                         </en>
                    </text>
               </xsl:for-each>
          </mycode>
     </xsl:template>
</xsl:stylesheet>

Ich freue mich über eure Hilfe! Viele Grüße, Janine

akzeptierte Antworten

  1. Hallo Janine,

    das Problem lässt sich generischer angehen mit einem Identitäts-Template für alles außer den a-Elementen, die separat behandelt werden:

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:fn="http://www.w3.org/2005/xpath-functions"
      exclude-result-prefixes="#all" version="2.0">
    
      <xsl:output method="xml" version="1.0" encoding="UTF-8"/>
    
      <xsl:template match="@* | node()">
        <xsl:copy>
          <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
      </xsl:template>
    
      <xsl:template match="a">
        <xsl:variable name="pn" select="fn:local-name(parent::node())"/>
        <a href="{@href}_{$pn}.html"><xsl:value-of select="."/></a>
      </xsl:template>
    
    </xsl:stylesheet>
    

    Falls das weitere "unbeteiligte" a-Elemente mitnehmen sollte, lassen sich die gesuchten Elemente auch separat ansprechen:

    <xsl:template match="a[parent::de]">
      <a href="{@href}_de.html"><xsl:value-of select="."/></a>
    </xsl:template>
    
    <xsl:template match="a[parent::en]">
      <a href="{@href}_en.html"><xsl:value-of select="."/></a>
    </xsl:template>
    

    Grüße,
    Thomas

    1. Hallo Thomas,

      vielen Dank für deine Lösung. Leider konnte ich ihn nicht so ganz in meinen Anwendungsfall übertragen. Meine Fragen dazu:

      1. Gibt es noch weitere Möglichkeiten, <template match="a"> zu schreiben? Ich habe mir hier mit <call-template name="link"> beholfen, weil das @match an dieser Stelle nicht funktioniert hat. Warum, verstehe ich leider nicht ganz.

      2. Ich habe anstatt auf parent::node() zuzugreifen, meinen globalen Parameter $lang verwendet, der Inhalt ist an dieser Stelle der gleiche. parent::node() funktioniert nicht, weil es sich in meinem Original-Code tatsächlich eher um den grandparent::node() handeln würde ;) Diesen kann man so nicht über XPath adressieren, oder?

      Anbei ein Auszug meiner Original-Quelldatei:

      <?xml version="1.0" encoding="iso-8859-1"?>
      <definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../xml/definitions.xsd">
      
      
      	<definition>
      		<term>
      			<de>Abweichung</de>
      			<en>Deviation</en>
      		</term>
      		<description>
      			<de>
      				<txt>Allgemein: Unterschied zwischen einem <a href="Merkmal">Merkmalswert</a> oder einem dem Merkmal zugeordneten Wert und einem Bezugswert</txt>
      				<txt>Bei einem quantitativen Merkmal: Merkmalswert oder ein dem Merkmal zugeordneter Wert minus Bezugswert</txt>
      			</de>
      			<en>
      				<txt>Generally: difference between the value of a <a href="characteristic">characteristic</a> or the value referenced to a characteristic and the benchmark</txt>
      				<txt>If quantitative value: value of a characteristic or the value referenced to a characteristic minus the benchmark</txt>
      			</en>
      		</description>
      	</definition>
      	
      	<definition src="ISO9000">
      		<term>
      			<de>Abweichungsgenehmigung</de>
      			<en>Deviation Permit</en>
      		</term>
      		<description>
      			<de>
      				<txt>Vor der Realisierung erteilte Erlaubnis, von ursprünglich festgelegten <a href="Anforderung">Anforderungen</a> an ein <a href="Produkt">Produkt</a> oder eine <a href="Dienstleistung">Dienstleistung</a> abzuweichen</txt>
      			</de>
      			<en>
      				<txt>Permission to depart from the originally specified <a href="requirement">requirements</a> of a <a href="product">product</a> or <a href="service">service</a> prior to its realization</txt>
      			</en>
      		</description>
      		<references>
      			<ref>GPV-101-070.030</ref>
      		</references>
      	</definition>	
      	
      	<definition>
      		<term>
      			<de>Action Item</de>
      			<en>Action Item</en>
      		</term>
      		<description>
      			<de>
      				<txt>Im Gegensatz zu CAR-Aktionen stehen Action Items nicht im Zusammenhang mit <a href="Korrektur">Korrektur</a>- und <a href="Vorbeugungsmaßnahme">Vorbeugungsmaßnahmen</a> einer <a href="Nichtkonformität">Nichtkonformität</a>. Ein verantwortlicher Mitarbeiter wird für die termingerechte Bearbeitung der Action Items benannt.</txt>
      			</de>
      			<en>
      				<txt>In contrast to CAR actions, action items are not related to <a href="correction">corrective</a> and <a href="preventive_action">preventive actions</a> of a <a href="nonconformity">nonconformity</a>. A responsible employee is named for the timely processing of the action items.</txt>
      			</en>
      		</description>
      		<references>
      			<ref>GPV-101-160.050</ref>
      		</references>
      	</definition>
      
      <!-- Hier folgen viele viele weitere Definitionen mit (oder auch ohne) Links in den <txt>-Elementen. -->
      
      </definitions>
      

      Und hier ein Auszug meiner XSL-Datei, die zunächst eine Übersicht aller Begriffe sowie für jedes <definition>-Element eine separate HTML-Datei schreibt. Mein Ziel ist nun, diese Beschreibungen untereinander zu verlinken.

      <?xml version="1.0" encoding="iso-8859-1"?>
      <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml" xmlns:redirect="http://xml.apache.org/xalan/redirect" extension-element-prefixes="redirect">
      	<xsl:param name="lang"/>
      	<xsl:param name="altlang"/>
      	<xsl:param name="destdir"/>
      	<xsl:variable select="document('../dev/xml/istrings.xml')" name="istrings"/>
      	<xsl:variable name="defsources" select="document('../dev/xml/defsources.xml')/defsources"/>
      	<xsl:variable name="gototop">window.parent.scroll(0,0);</xsl:variable>
      	
      	<xsl:output method="html" indent="yes"/>
      
      	<xsl:template match="/">
      		<xsl:apply-templates/>
      	</xsl:template>
      	
      	<xsl:template match="definitions">
      		<html>
      			<head>
      				<xsl:call-template name="head"/>
      			</head>
      			<body>
      				<!--Hier folgen Anweisungen für den HTML-Body der Übersichtsliste -->
      			</body>
      		</html>
      		<xsl:apply-templates/>
      	</xsl:template>
      	
      	<!-- Ausgelagert: Verlinkte Stylesheets und Skripte-->
      	<xsl:template name="head">
      		<!--Hier folgen Anweisungen für den HTML-Header -->
      	</xsl:template>
      	
      	<!-- Einzelne HTML-Dateien -->
      	<xsl:template match="definition">
      		<xsl:variable name="defname" select="translate(term/*[local-name()=$lang],' ;/','___')"/>
      		<xsl:variable name="defsource">
      			<!--Hier folgen Anweisungen für die Definition der Variablen -->
      		</xsl:variable>
      		<xsl:if test="string-length($defname)!=0">
      			<xsl:variable name="path" select="concat('\\ds-apps1-100\biotronik7\definitions\',$defname,'_',$lang,'.html')"/>
      			<xsl:result-document href="{$path}">
      				<html>
      					<head>
      						<!--Hier folgen Anweisungen für den HTML-Header -->
      					</head>
      					<body>
                    <!--Hier folgen weitere Anweisungen oberhalb der Tabelle -->
                    <!--Hier wird zunächst der deutsche Begriff und darunter die deutsche Beschreibung ausgegeben. -->
      							<table>
      								<tr>
      									<td class="kopf">
      										<xsl:value-of select="term/*[local-name()=$lang]"/>
      									</td>
      								</tr>
      							</table>
      						</div>
      						<div id="defcontent">
      							<xsl:for-each select="description/*[local-name()=$lang]/txt">
      								<xsl:choose>
                      <!--Hier folgen Formatierungsunterscheidungen für die <txt>-Elemente: Bulletpoints, Aufzählungen, kursive Markierungen etc. -->
      									<xsl:when test="starts-with(.,'- ')">
      										<p class="unorderedlist">&#8226; <xsl:value-of select="substring(.,3)"/></p>	
      									</xsl:when>
      									<xsl:when test="substring(.,2,1)='.'">
      										<p class="orderedlist"><xsl:value-of select="."/></p>	
      									</xsl:when>
      									<xsl:when test="@format='bold'">							 
      										<p class="bold"><xsl:value-of select="."/></p>
      									</xsl:when>
      									<xsl:when test="@format='italic'">							 
      										<p class="italic"><xsl:value-of select="."/></p>	
      									</xsl:when>
      									<xsl:otherwise>
      										<p class="normal"><xsl:value-of select="."/></p>	
      									</xsl:otherwise>
      								</xsl:choose>			
      							</xsl:for-each>	
      							<xsl:if test="$defsource!='*'">
      								<p class="defsource">
      									<xsl:value-of select="$istrings/istrings/text[@name='source']/* [local-name () = $lang]"/>
      									<xsl:text>: </xsl:text>
      									<xsl:value-of select="$defsources/source[@abbr=$defsource]"/>
      								</p>				
      							</xsl:if>
      						</div>
      						<br/>
      						<br/>
                    <!--Hier wird dann der englische Begriff und darunter die englische Beschreibung ausgegeben. -->
      						<div id="dokheader">
      							<table>
      								<tr>
      									<td class="kopf">
      										<xsl:value-of select="term/*[local-name()=$altlang]"/>
      									</td>
      								</tr>
      							</table>
      						</div>
      						<div id="defcontent">
      							<xsl:for-each select="description/*[local-name()=$altlang]/txt">
      								<xsl:choose>
                      <!-- Und hier das gleiche für die englischen Beschreibungen: Formatierungsunterscheidungen für die <txt>-Elemente. Hier plane ich aber nicht auf <xsl:value-of>, sondern will die <a>-Tags modifizieren. Deswegen rufe ich an dieser Stelle das Template name="link" auf und übergebe das gesamte <txt>-Element. -->
      									<xsl:when test="starts-with(.,'- ')">
      										<p class="unorderedlist">&#8226; <xsl:call-template name="link"><xsl:with-param name="text" select="substring(.,3)"/></xsl:call-template></p>	
      									</xsl:when>
      									<xsl:when test="substring(.,2,1)='.'">
      										<p class="orderedlist"><xsl:call-template name="link"><xsl:with-param name="text" select="."/></xsl:call-template></p>	
      									</xsl:when>
      									<xsl:when test="starts-with(., '_')">							 
      										<p class="def"><xsl:call-template name="link"><xsl:with-param name="text" select="substring(.,3)"/></xsl:call-template></p>										
      									</xsl:when>
      									<xsl:when test="@format='bold'">							 
      										<p class="bold"><xsl:call-template name="link"><xsl:with-param name="text" select="."/></xsl:call-template></p>
      									</xsl:when>
      									<xsl:when test="@format='italic'">							 
      										<p class="italic"><xsl:call-template name="link"><xsl:with-param name="text" select="."/></xsl:call-template></p>	
      									</xsl:when>
      									<xsl:otherwise>
      										<p class="normal"><xsl:call-template name="link"><xsl:with-param name="text" select="."/></xsl:call-template></p>	
      									</xsl:otherwise>
      								</xsl:choose>			
      							</xsl:for-each>	
      							<xsl:if test="$defsource!='*'">
      								<p class="defsource">
      									<xsl:value-of select="$istrings/istrings/text[@name='source']/* [local-name () = $altlang]"/>
      									<xsl:text>: </xsl:text>
      									<xsl:value-of select="$defsources/source[@abbr=$defsource]"/>
      								</p>				
      							</xsl:if>
      						</div>
      					</body>
      				</html>
      			</xsl:result-document>
      		</xsl:if>
      	</xsl:template>
      
      <!--An dieser Stelle kommt dein vorgeschlagenes Template.-->
      	<xsl:template name="link">
      		<xsl:param name="text"/>
      		<xsl:for-each select="a">
      			<a href="{@href}_{$lang}.html"><xsl:value-of select="$text"/></a>
      		</xsl:for-each>
      	</xsl:template>
        
      </xsl:stylesheet>
      

      Leider funktioniert die Ausgabe so nicht, es wird alles dreifach ausgegeben, also z.B. für den Begriff "Abweichungsgenehmigung":

      			<p class="normal">
      				<a href="requirement_de.html">Permission to depart from the originally specified requirements of a product or service prior to its realization</a>
      				<a href="product_de.html">Permission to depart from the originally specified requirements of a product or service prior to its realization</a>
      				<a href="service_de.html">Permission to depart from the originally specified requirements of a product or service prior to its realization</a>
      			</p>
      

      Kannst du mir erklären, warum?

      Besten Dank bereits für die Mühe, die du dir bisher gemacht hast! Deine Lösung funktioniert prima, nur leider kann ich sie so nicht anwenden...

      Viele Grüße, Janine

      1. Hallo Janine,

        meine Antwort bezog sich auf direkt auf Deine Problemschilderung.

        Ich kann Deine weiteren Fragen so nicht beantworten. Der Code ist durch die fehlenden externen XML-Dokumente [document('…')] gar nicht nachvollziehbar. Zudem ist im XSLT-Code ein Fehler. Konnte ihn durch ein zusätzliches öffnendes <div> nach <body> reparieren, aber auch das führt mit Saxon-HE 10.0 nur zu:

        <!DOCTYPE HTML>
        <html xmlns="http://www.w3.org/1999/xhtml">
           <head>
              <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
           </head>
           <body></body>
        </html>
        

        Erstelle einen nachvollziehbaren Testcase, der das exakte Problem abbildet. Sonst artet das nur in wilde Kaffeesatzleserei aus.

        Die benannten xsl:call-template|s erscheinen mir auch nicht zielführend. Ich würde von vorn beginnen und auf xsl:template / xsl:apply-templates setzen sowie xsl:for-each nur für direkte Abfolgen / Aufzählungen im jeweiligen Kontext verwenden.

        Auch die Erzeugung von Listen via Absätzen mit Unicode-Bullets wäre gegenüber von ul-/li-/Listen zu hinterfragen.

        Grüße,
        Thomas

        1. Hallo Thomas,

          ich habe diesen (und noch viele weitere) Codes von meinem Vorgänger geerbt und muss sie nun pflegen, verbessern und erweitern. Ich versuche immer, wenn ich an einem der 1.000 XSL-Stylesheets unter meiner Obhut dran sitze, den Code durch Kommentare besser lesbar zu gestalten und Konventionen zu berücksichtigen. Von daher danke ich dir für deine Kommentare, ich werde die HTML-Seite noch etwas verbessern. Es ist ohnehin geplant, demnächst ein neues Layout über die Seiten zu legen, da lassen sich <ul><li> besser formatieren.

          Auch dein Hinweis zu xsl:apply-templates hat mir schon weitergeholfen. Ich habe mich in die Doku dazu nochmal tiefer eingelesen und verstehe nun den Sinn dahinter besser. Man kann xsl:apply-templates überall da verwenden, wo ggf. noch weitere Kindelemente kommen, die mithilfe eines eigenen <xsl:template match="kindelement"> angesprochen werden. Bisher habe ich dafür tatsächlich immer for-each-Schleifen verwendet. Diese Einsicht eröffnet mir nun viele weitere Türen, besten Dank dafür :)

          Ich habe, ausgehend von deiner ursprünglichen Lösung, nun den folgenden Testcode entwickelt, der auch für die komplexere Quelldatei funktioniert:

          <?xml version="1.0" encoding="UTF-8"?>
          <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:fn="http://www.w3.org/2005/xpath-functions" exclude-result-prefixes="#all" version="2.0">
          
          	<xsl:variable name="lang" select="'de'"/>
          	<xsl:variable name="altlang" select="'en'"/>
          	<xsl:output method="html" indent="yes"/>
          	
          	<xsl:template match="/">
          		<xsl:apply-templates/>
          	</xsl:template>
          	
          	<xsl:template match="definitions">
          		<html>
          			<head/>
          			<body>
          				<div id="dokheader">
          					<table>
          						<tr>
          							<td>
          								<xsl:value-of select="definition[1]/term/*[local-name()=$lang]"/>
          							</td>
          						</tr>
          					</table>
          				</div>
          				<div id="defcontent">
          					<xsl:for-each select="definition[1]/description/*[local-name()=$lang]/txt">
          						<xsl:choose>
          							<xsl:when test="starts-with(.,'- ')">
          								<p class="unorderedlist"><xsl:copy><xsl:apply-templates select="substring(.,3)"/></xsl:copy></p>
          							</xsl:when>
          							<xsl:when test="substring(.,2,1)='.'">
          								<p class="orderedlist"><xsl:copy><xsl:apply-templates select="."/></xsl:copy></p>
          							</xsl:when>
          							<xsl:when test="@format='bold'">
          								<p class="bold"><xsl:copy><xsl:apply-templates select="."/></xsl:copy></p>
          							</xsl:when>
          							<xsl:when test="@format='italic'">
          								<p class="italic"><xsl:copy><xsl:apply-templates select="."/></xsl:copy></p>
          							</xsl:when>
          							<xsl:otherwise>
          								<p class="normal"><xsl:copy><xsl:apply-templates select="."/></xsl:copy></p>
          							</xsl:otherwise>
          						</xsl:choose>
          					</xsl:for-each>
          				</div>
          			</body>
          		</html>
          	</xsl:template>
          	
          	<xsl:template match="a">
          			<a href="{@href}_{$lang}.html"><xsl:value-of select="."/></a>
          	</xsl:template>
          	
          </xsl:stylesheet>
          
          

          Danke dir, Thomas, dass du immer wieder auf meine Beiträge antwortest und mir viele hilfreiche Hinweise gibst! Ich lerne noch... zwar langsam, aber es geht voran. ;)

          Viele Grüße und dir ein schönes Osterfest, Janine.