Kieran Ojakangas
Kieran Ojakangas

Reputation: 535

XSLT - Copy Parent Into Children Nodes

I need to move the copy the parent node into multiple children nodes, WITHOUT copying said children itself in the process. Here's an example XML:

<FoodGroups>
   <Nutritional>Yes</Nutritional>
   <Artifical>No</Artificial>
   <FoodGroup>
      <Fruit>Grape</Fruit>
      <Fruit>Apple</Fruit>
   </FoodGroup>
   <FoodGroup>
      <Vegetable>Carrot</Vegetable>
      <Vegetable>Potato</Vegetable>
   <FoodGroup>
</FoodGroups>

When the transformation finishes, I need the XML to look like this:

<xml>
   <FoodGroup>
      <Fruit>Grape</Fruit>
      <Fruit>Apple</Fruit>
      <FoodGroups>
         <Nutritional>Yes</Nutritional>
         <Articial>No</Artificial>
      </FoodGroups>
   </FoodGroup>
   <FoodGroup>
      <Vegetable>Carrot</Vegetable>
      <Vegetable>Potato</Vegetable>
      <FoodGroups>
         <Nutritional>Yes</Nutritional>
         <Articial>No</Artificial>
      </FoodGroups>
   <FoodGroup>
</xml>

I've tried using the following XML:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl">
<xsl:output omit-xml-declaration="yes"
method="xml"
indent="yes"/>

<xsl:template match="/">
<xml>
<xsl:copy>
    <xsl:apply-templates select="//FoodGroup"/>
</xsl:copy>
</xml>
</xsl:template>

<xsl:template match="//FoodGroup">
<FoodGroup>
    <xsl:copy-of select="./*"/>
    <xsl:copy-of select="//FoodGroups/*[not(child::FoodGroup)]"/>
</FoodGroup>
</xsl:template>

</xsl:stylesheet>

However, I get the following output (the children nodes COMPLETELY re-copy - I don't want this):

<xml>
   <FoodGroup>
      <Fruit>Grape</Fruit>
      <Fruit>Apple</Fruit>
      <FoodGroups>
         <Nutritional>Yes</Nutritional>
         <Artifical>No</Artificial>
         <FoodGroup>
            <Fruit>Grape</Fruit>
            <Fruit>Apple</Fruit>
         </FoodGroup>
         <FoodGroup>
            <Vegetable>Carrot</Vegetable>
            <Vegetable>Potato</Vegetable>
         <FoodGroup>
      </FoodGroups>
   </FoodGroup>
   <FoodGroup>
      <Vegetable>Carrot</Vegetable>
      <Vegetable>Potato</Vegetable>
      <FoodGroups>
         <Nutritional>Yes</Nutritional>
         <Artifical>No</Artificial>
         <FoodGroup>
            <Fruit>Grape</Fruit>
            <Fruit>Apple</Fruit>
         </FoodGroup>
         <FoodGroup>
            <Vegetable>Carrot</Vegetable>
            <Vegetable>Potato</Vegetable>
         <FoodGroup>
      </FoodGroups>
   <FoodGroup>
</xml>

Can this kind of parent to children copying be done? Is it possible? Part of the difficulty is that sometimes these children are more than one level deep.

Upvotes: 0

Views: 47

Answers (2)

michael.hor257k
michael.hor257k

Reputation: 116972

Why not simply (and also more efficiently):

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:template match="/FoodGroups">
    <xml>
        <xsl:variable name="common" select="*[not(self::FoodGroup)]" />
        <xsl:for-each select="FoodGroup">
            <xsl:copy>
                <xsl:copy-of select="*"/>
                <FoodGroups>
                    <xsl:copy-of select="$common"/>
                </FoodGroups>
            </xsl:copy>
        </xsl:for-each>
    </xml>
</xsl:template>

</xsl:stylesheet>

Upvotes: 0

Martin Honnen
Martin Honnen

Reputation: 167471

The following seems to do the job:

<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">

  <xsl:output method="xml" indent="yes"/>
  <xsl:strip-space elements="*"/>

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

  <xsl:template match="/">
    <xml>
      <xsl:apply-templates/>
    </xml>
  </xsl:template>
  
  <xsl:template match="FoodGroups">
    <xsl:apply-templates/>
  </xsl:template>
  
  <xsl:template match="FoodGroups/*[not(self::FoodGroup)]"/>
  
  <xsl:template match="FoodGroup">
    <xsl:copy>
      <xsl:apply-templates/>
      <FoodGroups>
        <xsl:copy-of select="ancestor::FoodGroups/*[not(self::FoodGroup)]"/>
      </FoodGroups>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

Upvotes: 0

Related Questions