1
votes

I'm trying to replace/modify some of the text nodes in an XML document by matching them with an elements attribute in an external file.

That's my XML File:

<root>
    <level_1>
        <item_1>xxxxx</item_1>
        <item_2>yyyyy</item_2>
        <item_3>zzzzz</item_3>
    </level_1>
</root>

XSLT code:

<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">
    <xsl:param name="additionalInfoPath"/>
    <xsl:output indent="yes" />
    <xsl:function name="functx:contains-any-of" as="xs:boolean" 
              xmlns:functx="http://www.functx.com" >
  <xsl:param name="arg" as="xs:string?"/> 
  <xsl:param name="searchStrings" as="xs:string*"/> 

  <xsl:sequence select=" 
   some $searchString in $searchStrings
   satisfies contains($arg,$searchString)
 "/>
</xsl:function>
    <xsl:variable name="additionalInfo" select="doc($additionalInfoPath)"/>
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="*[functx:contains-any-of(string-join(ancestor-or-self::*/upper-case(local-name()),'/'),$additionalInfo//AdditionalInfoData[@Type='ExtraInfo']/InfoDataValue/@TargetElement)]/text()">
                <xsl:value-of select="$additionalInfo//AdditionalInfoData[@Type='ExtraInfo']/InfoDataValue[contains(string-join(current()/ancestor-or-self::*/upper-case(local-name()),'/'),@TargetElement)]/text()"/>
    </xsl:template>
</xsl:stylesheet>

And the external XML document providing the values to for replacing:

  <AdditionalInfoRoot xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <AdditionalInfoData Type="ExtraInfo">
    <InfoDataValue Name="ITEM_1" TargetElement="ITEM_1" TargetElementValue="">111111</InfoDataValue>
    <InfoDataValue Name="LEVEL_1$ITEM_3" TargetElement="LEVEL_1/ITEM_3" TargetElementValue="">333333</InfoDataValue>
  </AdditionalInfoData>
</AdditionalInfoRoot>

Note that in the external document the name of the target elements are in upper case and I don't have control over that, and I need to use contains in a full xpath to the root because of things like "LEVEL_1/ITEM_3".

This solution is working but it's somehow cumbersome and I think there must be a better solution I'm not able to devise...

Any suggestions or hints to improve this are very welcome!

Thxs Vlax

1
So, what is the wanted result from the transformation? Please, edit the question and provide this.Dimitre Novatchev
@Dimitre: Thanks for answer you got the result right, sorry for the delayed answer.Vlax

1 Answers

0
votes

This might be simpler:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:variable name="vLookupDoc">
   <t>
     <e path="ITEM_1">111111</e>
     <e path="LEVEL_1/ITEM_3">333333</e>
   </t> 
 </xsl:variable>

 <xsl:template match="node()[not(self::text())]|@*">
   <xsl:copy>
     <xsl:apply-templates select="node()|@*"/>
   </xsl:copy>
 </xsl:template>

 <xsl:template match="text()">
  <xsl:variable name="vRep" select=
   "$vLookupDoc/*/e
      [ends-with(
        string-join(current()/ancestor::*[parent::*]/lower-case(name(.)), '/'),
        lower-case(@path)
                 )
      ]
       /string(.)
   "/>

   <xsl:sequence select="(., $vRep)[last()]"></xsl:sequence>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<root>
    <level_1>
        <item_1>xxxxx</item_1>
        <item_2>yyyyy</item_2>
        <item_3>zzzzz</item_3>
    </level_1>
</root>

the wanted, correct result is produced:

<root>
      <level_1>
            <item_1>111111</item_1>
            <item_2>yyyyy</item_2>
            <item_3>333333</item_3>
      </level_1>
</root>