Urs Christen
Urs Christen

Reputation: 3

XSLT Transformation connect nodes with same names with parent node

I want to transform the following XML-data to the desired output shown below - without success until now. So I hope of an input from an expert relating the xsl. Thank you in advance.

Input XML-Data

<?xml version="1.0" encoding="utf-8"?>
<pages>
   <page number="1">
     <pageArticles>
         <articleID id="100"/>
         <articleID id="200"/>
         <articleID id="300"/>
     </pageArticles>
   </page>
   <page number="5">
      <pageArticles/>
   </page>
   <page number="9">
      <pageArticles>
         <articleID id="400"/>
         <articleID id="500"/>                 
      </pageArticles>
   </page>
</pages>

Desired Ouput

<?xml version="1.0" encoding="utf-8"?>
<pages>
  <page>
     <number>1</number>
     <articleID>100</articleID>
  </page>
  <page>
     <number>1</number>
     <articleID>200</articleID>
  </page>
  <page>
     <number>1</number>
     <articleID>300</articleID>
  </page>
  <page>
     <number>5</number>
     <articleID></articleID>
  </page>
  <page>
     <number>9</number>
     <articleID>400</articleID>
  </page>
  <page>
     <number>9</number>
     <articleID>500</articleID>
  </page>
</pages>

Many thanks in advance for any assistance! Urs

Upvotes: 0

Views: 68

Answers (2)

Martin Honnen
Martin Honnen

Reputation: 167716

Write a template for those articleID elements to collect the number from the grandparent and add a template for empty pageArticles:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    expand-text="yes"
    version="3.0">

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

  <xsl:template match="pages">
      <xsl:copy>
          <xsl:apply-templates select="page/pageArticles"/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="pageArticles[not(has-children())]">
      <page>
          <number>{../@number}</number>
          <articleID/>
      </page>
  </xsl:template>

  <xsl:template match="articleID">
      <page>
          <number>{../../@number}</number>
          <articleID>{@id}</articleID>
      </page>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/bFDb2BT and the above is XSLT 3. In earlier version of XSLT you have to replace the text value templates (e.g. {../@number}) with xsl:value-of (e.g. <xsl:value-of select="../@number"/>) and the predicate [not(has-children())] with [not(node()].

Upvotes: 0

zx485
zx485

Reputation: 29052

This XSLT-1.0 stylesheet does the job:

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

  <xsl:template match="text()" />

  <xsl:template match="/pages">
    <xsl:copy>
        <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="//articleID | pageArticles[not(*)]">
    <page>
        <number><xsl:value-of select="ancestor::page/@number[1]" /></number>
        <articleID><xsl:value-of select="@id" /></articleID>
    </page>
  </xsl:template>

</xsl:stylesheet>

Output is:

<pages>
   <page>
      <number>1</number>
      <articleID>100</articleID>
   </page>
   <page>
      <number>1</number>
      <articleID>200</articleID>
   </page>
   <page>
      <number>1</number>
      <articleID>300</articleID>
   </page>
   <page>
      <number>5</number>
      <articleID/>
   </page>
   <page>
      <number>9</number>
      <articleID>400</articleID>
   </page>
   <page>
      <number>9</number>
      <articleID>500</articleID>
   </page>
</pages>

Upvotes: 1

Related Questions