0
votes

I'm new with xml, it's very difficult for me to understand how XSLT works. Can you help me fix some errors in my xslt file? I'd like to transform this input file :

<?xml version="1.0" encoding="utf-8"?>
<data>
    <parent><string >AAA</string></parent>
    <nb><string >2</string></nb>
    <child1>aaa-1</child1>
    <child1>aaa-2</child1>
    <parent><string >BBB</string></parent>
    <nb><string>1</string></nb>
    <child2>bbb-1</child2>
    <parent><string >CCC</string></parent>
    <nb><string >0</string></nb>
</data>

into :

<?xml version="1.0" encoding="utf-8"?>
<data>
    <parent>
        <string >AAA</string>
        <nb><string >2</string></nb>
        <child1>aaa-1</child1>
        <child1>aaa-2</child1></parent>
    <parent>
        <string >BBB</string>
        <nb><string >1</string></nb>
        <child2>bbb-1</child2></parent>
    <parent>
        <string >CCC</string>
        <nb><string >0</string></nb></parent>
</data>

The rules are :

  1. The "nb" node indicates the number of childs for each parent. It can be 0.

  2. The nodes "child1" and "child2" are different. They are complex, with nested loops etc. My above input file is simplified for demo.
    I think, I've to use the 'copy' instruction.

  3. What is difficult for me, is that :

    • for each parent, I've to read the defined number ("nb") of child nodes following current parent
    • when the "parent" value is 'AAA' then I've to read the "child1"
      when the "parent" value is !=AAAA then I've to read the "childe2" node.

Here is my XSLT file, the result is not exactly as expected :

    <?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
    <xsl:template match="/">
        <xsl:comment>--- </xsl:comment>
        <xsl:comment>1 : parent nodes </xsl:comment>
        <xsl:for-each select ="/data/parent">
            <p> 
                <string>
                    <xsl:value-of select="string"/>
                </string>
                <xsl:comment>======================= </xsl:comment>
                <xsl:comment>2 : nb nodes (how many childs for a parent ) </xsl:comment>
                <xsl:for-each select ="/data/nb">
                    <xsl:if test="((position() &lt; 2) and (normalize-space(position() &gt;= 1)))">
                        <xsl:comment>Ex. for tThe first value only </xsl:comment>
                        <xsl:comment>How to do a dynamic test here (expected : AAA->3 (first nb value), BBB->1 (second nb value) ...)  ?</xsl:comment>
                        <xsl:comment>How to synchronise loop on parent and nb  ?</xsl:comment>
                        <nb>
                            <string>
                                <xsl:value-of select="string"/>
                            </string>
                        </nb>
                    </xsl:if>
                </xsl:for-each>
                <xsl:comment>======================= </xsl:comment>
                <xsl:comment>3 : child nodes </xsl:comment>
                <xsl:comment>How to manage the position and number of nodes to read ?</xsl:comment>
                <xsl:comment>Test 'string =AAA' is KO : always child2 </xsl:comment>
                <xsl:choose>
                    <xsl:when test='string =AAA'>
                        <xsl:copy-of select="/*/child1" />
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:copy-of select="/*/child2" />
                    </xsl:otherwise>
                </xsl:choose>               
            </p>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

Thanks in advance
Best regards

2
Sorry, it's doneasx

2 Answers

1
votes

If the data is consistent, that is, if there is always the number of child1/child2 elements the nb element indicates and the parent value mandates then you can simply process the different types of parent elements with

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

  <xsl:output indent="yes"/>

  <xsl:template match="data">
      <xsl:copy>
          <xsl:apply-templates select="parent"/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="parent[string = 'AAA']">
      <xsl:copy>
          <xsl:variable name="n" select="following-sibling::nb[1]"/>
          <xsl:copy-of select="string, following-sibling::nb[1], following-sibling::child1[position() &lt;= $n]"/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="parent[string != 'AAA']">
      <xsl:copy>
          <xsl:variable name="n" select="following-sibling::nb[1]"/>
          <xsl:copy-of select="string, following-sibling::nb[1], following-sibling::child2[position() &lt;= $n]"/>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/94hvTzk/0 has that sample, you could as well do that in XSLT 1 as

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

  <xsl:output indent="yes"/>

  <xsl:template match="data">
      <xsl:copy>
          <xsl:apply-templates select="parent"/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="parent[string = 'AAA']">
      <xsl:copy>
          <xsl:variable name="n" select="following-sibling::nb[1]"/>
          <xsl:copy-of select="string | following-sibling::nb[1] | following-sibling::child1[position() &lt;= $n]"/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match="parent[string != 'AAA']">
      <xsl:copy>
          <xsl:variable name="n" select="following-sibling::nb[1]"/>
          <xsl:copy-of select="string | following-sibling::nb[1] | following-sibling::child2[position() &lt;= $n]"/>
      </xsl:copy>
  </xsl:template>

</xsl:stylesheet>

online at https://xsltfiddle.liberty-development.net/94hvTzk/1

If the data is not consistent then at least in XSLT 2 or 3 it would be an easy job for for-each-group select="*" group-starting-with="parent" in the context of the data element to identify the elements that belong together and create the parent wrapper in the result.

1
votes

With XSLT 3.0 this is simply

<xsl:transform version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="data">
    <data>
      <xsl:for-each-group select="*" group-starting-with="parent">
        <parent>
          <xsl:copy-of select="*, tail(current-group())"/>
        </parent>
      </xsl:for-each-group>
    </data>
  </xsl:template>
</xsl:transform>