-1

I have a XML structure where the XML schema is irregular/not formatted. The structure looks like this-

<Host> 
<element1>type0</element1>
<element2>Fruits</element2>
....
<elementn>Price0</elementn>   
   <Menu>
   <NodeA>
    <element1>type1</element1>
    <element2>Fruits</element2>
    ....
    <elementn>Price1</elementn>
    <Menu>
    <NodeB>
      <element1>type2</element1>
      <element2>Fruits</element2>
      ....
      <elementn>Price2</elementn>
      <Menu>
      <NodeC>
        <element1>type3</element1>
        <element2>Fruits</element2>
        ....
        <elementn>Price3</elementn>
        <Menu>
        <NodeD>
          <Element1>type4</element1>
          <Element2>Vegetables</Element2>
          ....
          <Elementn>Price4</elementn>  
        </NodeD>
        </Menu>    
      </NodeC>
      </Menu>
     </NodeB>
     </Menu>
  </NodeA>
  <NodeE>
    <element1>type5</element1>
    <element2>Fruits</element2>
    ....
    <elementn>Price5</elementn>
    <Menu>
    <NodeF>
      <element1>type6</element1>
      <element2>Vegetables</element2>
      ....
      <elementn>Price6</elementn>
    </NodeF> 
    </Menu>  
  </NodeE>  
  </Menu> 
</Host>

Now my expected XML is as follows-

a) if <element2> == fruits in all the nodes, I need XML schema as follows. I may include or exclude the below n elements right under host -

`<element1>type0</element>
 <element2>Fruits</element2>
 ....
 <elementn>Price0</elementn>`

.Expected Result -

<Host> 
  <NodeA>
    <element1>type1</element1>
    <element2>Fruits</element2>
    ....
    <elementn>Price1</elementn>
  </NodeA>
  <NodeB>
    <element1>type2</element1>
    <element2>Fruits</element2>
    ....
    <elementn>Price2</elementn>
  </NodeB>
  <NodeC>
    <element1>type3</element1>
    <element2>Fruits</element2>
    ....
    <elementn>Price3</elementn>
  </NodeC>
  <NodeE>
    <element1>type5</element1>
    <element2>Fruits</element2>
    ....
    <elementn>Price5</elementn>
  </NodeE>    
</Host>

b) if <element2> == vegetables in all the nodes, I need XML schema as follows

Note: <element2> == Vegetables is always at the last node in the schema

<Host>
  <NodeD>
    <element1>type4</element1>
    <element2>Vegetables</element2>
    ....
    <elementn>Price4</elementn>
  </NodeD>    
  <NodeF>
    <element1>type6</element1>
    <element2>Vegetables</element2>
    ....
    <elementn>Price6</elementn>
  </NodeF>
</Host>

Any help for getting the above XML formats through XSLT would be a great help.

QA Testing
  • 57
  • 7
  • Neither your input nor output are valid XML, and thus not processable using XML tools. Also, your condition statements _" if ` == fruits` in all the nodes, the expected XML will be as follows"_ leave many possibilities uncovered, such as some values being `fruits` and some being `vegetables`. The question is unclear. – Jim Garrison Mar 09 '17 at 05:16
  • Still invalid XML. For example `` has no closing tag. – Jim Garrison Mar 09 '17 at 05:38
  • Your point (a) refers to the case of `fruits in all the nodes`. Your point (b) refers to the case of `vegetables in all the nodes`. But your XML has both fruits and vegetables, so points (a) and (b) do not apply. What should happen in the case where the XML has both fruits and vegetables? Do you want multiple XML documents produced? Or should they are still appear in the same output with fruits before vegetables? Thanks. – Tim C Mar 09 '17 at 09:12
  • Why the tags [tag:xslt-2.0] and [libxslt] for the same question? Libxslt only supports XSLT 1.0. – Martin Honnen Mar 09 '17 at 09:33
  • Hi @TimC I need 2 XML output documents for the given 2 conditions by using 2 XSLTs. – QA Testing Mar 09 '17 at 10:59

1 Answers1

2

If you want 2 separate document, you don't actually need 2 XSLTs. You can use one XSLT but with a parameter

<xsl:param name="element2" select="'Fruits'" />

(Here 'Fruits' is just the default value, should the parameter not be specified by the calling application).

You would start off by selecting the nodes which have element2 equal to the parameter (Do note XML and XSLT is case-sensitive, so element2 is not the same as Element2 in your XML, but I assumed that was a typo in your XML).

<xsl:apply-templates select="//*[element2=$element2]"/>

You would also need a template to ensure when a node is matched, it does not copy the child nodes...

<xsl:template match="*[element2]">
    <xsl:copy>
        <xsl:apply-templates select="*[not(*)]" />
    </xsl:copy>
</xsl:template>

The other nodes would be handled by the identity template.

Try this XSLT...

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

    <xsl:param name="element2" select="'Fruits'" />

    <xsl:template match="/*">
        <xsl:copy>
            <xsl:apply-templates select="//*[element2=$element2]" mode="copy"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="*[element2]" mode="copy">
        <xsl:copy>
            <xsl:apply-templates select="*[not(*)]" mode="copy"/>
        </xsl:copy>
    </xsl:template>

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

Note that if you were using XSLT 2.0, you could create multiple documents in one call, using xsl:for-each-group to get the distinct groups, and xsl:result-document to create a file for each.

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

    <xsl:template match="/*">
        <xsl:for-each-group select="//*[element2]" group-by="element2">
            <xsl:result-document href="{current-grouping-key()}.xml" method="xml">
                <Host>
                    <xsl:apply-templates select="current-group()" mode="copy" />
                </Host>
            </xsl:result-document>
        </xsl:for-each-group>
    </xsl:template>

    <xsl:template match="*[element2]" mode="copy">
        <xsl:copy>
            <xsl:apply-templates select="*[not(*)]" mode="copy"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="@*|node()" mode="copy">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()" mode="copy" />
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>
Tim C
  • 70,053
  • 14
  • 74
  • 93
  • I have tried 1st XSLT and i see an error - ' Ambiguous rule match for /root Matches both "*[element2]" on line 12 of and "/*" on line 6 of ' – QA Testing Mar 13 '17 at 11:35
  • I have tried 2nd XSLT and i see an error - ' Ambiguous rule match for /root Matches both "*[element2]" on line 14 of and "/*" on line 4 of ' – QA Testing Mar 13 '17 at 11:44
  • I've made a correction to my answer to handle conflicting templates. – Tim C Mar 13 '17 at 12:19
  • I see the below error now '8 : Too many nested apply-templates calls. The stylesheet may be looping.' – QA Testing Mar 13 '17 at 13:20
  • I've made another change to my answer, although it does sound like your actual XML may be different to what is shown in your question. Thanks – Tim C Mar 13 '17 at 14:14
  • Now i see that the XSLT filtering has not been applied. Instead it has copied all the nodes a number of times. – QA Testing Mar 13 '17 at 14:49
  • Adding to my question, My XML structure has around 3000 nodes, where the node has a path of child nodes ranging from minimum 3 sub nodes to max 10 sub nodes. If you refer the above XML for this example - Node A has sub nodes range from 3-10(NodeB, NodeC,.... NodeK). – QA Testing Mar 13 '17 at 15:05
  • Hi @Tim C, I have modified the XML in my question. Please help me with this. – QA Testing Mar 13 '17 at 16:05
  • I have made another "final" change, to cope with the Menu tag you have added to your XML. In future, can you make sure your XML samples accurately reflect your real XML. Thank you. – Tim C Mar 13 '17 at 17:24
  • Hi @TimC, Is there a way to select only particular elements from the given xml. Now the XSLT is returning all the elements in the node. Say I want only from all the selected nodes where = 'Fruits' is applied. – QA Testing Mar 17 '17 at 11:26
  • In the template matching `*[element2]` you would just do ``. – Tim C Mar 17 '17 at 14:05