简体   繁体   中英

XSLT / XPath replace specific nodes

I have the following structure of xml data to transform:

<root>
        <main1>
            <page>
                <text-body>
                    <title>K1</title>
                    <subtitle>Text</subtitle>
                </text-body>
                <text-body>
                    <title>Text</title>
                    <subtitel>Text</subtitel>
                </text-body>
                <text-body>
                    <title>Text</title>
                    <subtitel>Text</subtitel>
                </text-body>
            </page>
            <page>
                <text-body>
                    <title>K2</title>
                    <subtitel>Text</subtitel>
                </text-body>
                <text-body>
                    <title>Text</title>
                    <subtitel>Text</subtitel>
                </text-body>
            </page>
            <page>
                <text-body>
                    <title>K3</title>
                    <subtitel>Text</subtitel>
                </text-body>
                <text-body>
                    <title>Text</title>
                    <subtitel>Text</subtitel>
                </text-body>
            </page>
        </main1>
        <main2>
            <text-body>
                <title>B</title>
                <subtitle>B</subtitle>
                <body>B</body>
            </text-body>
            <text-body>
                <title>C</title>
                <subtitle>C</subtitle>
                <body>C</body>
            </text-body>
            <text-body>
                <title>D</title>
                <subtitle>D</subtitle>
                <body>D</body>
            </text-body>
        </main2>
 </root>

And I need to replace the data in main1/text-body with titles K1, K2, K3 with the data in main2/text-body, but keep the other text-body elements in main1. The output should look like this:

<root>
        <main1>
            <page>
                <text-body>
                    <title>B</title>
                    <subtitle>B</subtitle>
                    <body>B</body>
                </text-body>
                <text-body>
                    <title>Text</title>
                    <subtitel>Text</subtitel>
                </text-body>
                <text-body>
                    <title>Text</title>
                    <subtitel>Text</subtitel>
                </text-body>
            </page>
            <page>
                <text-body>
                    <title>C</title>
                    <subtitle>C</subtitle>
                    <body>C</body>
                </text-body>
                <text-body>
                    <title>Text</title>
                    <subtitel>Text</subtitel>
                </text-body>
            </page>
            <page>
                <text-body>
                    <title>D</title>
                    <subtitle>D</subtitle>
                    <body>D</body>
                </text-body>
                <text-body>
                    <title>Text</title>
                    <subtitel>Text</subtitel>
                </text-body>
            </page>
        </main1>
        <main2>
            <text-body>
                <title>B</title>
                <subtitle>B</subtitle>
                <body>B</body>
            </text-body>
            <text-body>
                <title>C</title>
                <subtitle>C</subtitle>
                <body>C</body>
            </text-body>
            <text-body>
                <title>D</title>
                <subtitle>D</subtitle>
                <body>D</body>
            </text-body>
        </main2>
 </root>

I have the following xsl-code:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="xml"/>

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

        <xsl:template match="main1/page/text-body">
            <xsl:param name="count" select="title/substring(.,2,1)"/>
            <xsl:if test="title/substring(.,1,1)='K'">
               <xsl:copy-of select="/root/main2/text-body[$count]"/>
            </xsl:if>
        </xsl:template>

    </xsl:stylesheet>

I try to select the number in the title and to check, if there is a K in the text. But it doesn´t work. And I have no idea how to keep the other text-body elements. Here is the current output:

 <main1>
            <page>
                <text-body>
                <title>B</title>
                <subtitle>B</subtitle>
                <body>B</body>
            </text-body><text-body>
                <title>C</title>
                <subtitle>C</subtitle>
                <body>C</body>
            </text-body><text-body>
                <title>D</title>
                <subtitle>D</subtitle>
                <body>D</body>
            </text-body>


            </page>
            <page>
                <text-body>
                <title>B</title>
                <subtitle>B</subtitle>
                <body>B</body>
            </text-body><text-body>
                <title>C</title>
                <subtitle>C</subtitle>
                <body>C</body>
            </text-body><text-body>
                <title>D</title>
                <subtitle>D</subtitle>
                <body>D</body>
            </text-body>

            </page>
            <page>
                <text-body>
                <title>B</title>
                <subtitle>B</subtitle>
                <body>B</body>
            </text-body><text-body>
                <title>C</title>
                <subtitle>C</subtitle>
                <body>C</body>
            </text-body><text-body>
                <title>D</title>
                <subtitle>D</subtitle>
                <body>D</body>
            </text-body>

            </page>
        </main1>
        <main2>
            <text-body>
                <title>B</title>
                <subtitle>B</subtitle>
                <body>B</body>
            </text-body>
            <text-body>
                <title>C</title>
                <subtitle>C</subtitle>
                <body>C</body>
            </text-body>
            <text-body>
                <title>D</title>
                <subtitle>D</subtitle>
                <body>D</body>
            </text-body>
        </main2>
 </root>

Please help!

Use a predicate to match only the text-body elements you really want to exchange. As you're using XSLT 2.0, you can use a regular expression in the predicate:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xsl:output method="xml"/>

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

  <xsl:template match="main1/page/text-body[matches(title,'^K\d+$')]">
    <xsl:variable name="count" select="xs:integer(title/substring(.,2))"/>
    <xsl:copy-of select="/root/main2/text-body[$count]"/>
  </xsl:template>

</xsl:stylesheet>

The main problem is that your count parameter is set to be a string, but if you want to use in as an index, it should be a number, so you will need to do this

<xsl:copy-of select="/root/main2/text-body[number($count)]"/>

Also, you probably don't want to use an xsl:if here, because with your current template, it will not output text-body elements when the if condition is false. You should actually move the test to be part of the template match.

<xsl:template match="main1/page/text-body[title/substring(.,1,1)='K']">

Here's the full XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
   <xsl:output method="xml"/>

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

   <xsl:template match="main1/page/text-body[title/substring(.,1,1)='K']">
      <xsl:param name="count" select="title/substring(.,2,1)"/>
      <xsl:copy-of select="/root/main2/text-body[number($count)]"/>
   </xsl:template>
</xsl:stylesheet>

This should produce your expected output.

If your are interested in a different approach, you could chose to define an xsl:key to look up the main2/text-body elements

<xsl:key name="main2" 
     match="main2/text-body" 
     use="concat('K', count(preceding-sibling::text-body) + 1)" />

That way, you can remove the need for substring by using the following template for matching the main1/page/text-body elements instead

 <xsl:template match="main1/page/text-body[key('main2', title)]">
    <xsl:copy-of select="key('main2', title)"/>
 </xsl:template>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM