1

Im an XSLT 2.0 beginner and trying to find a way to split big xml file into multiple chunks based on the condition.

Sample XML:

<?xml version="1.0" encoding="UTF-8"?>
<players>
    <player>
        <id>1</id>
        <fname>Vcut</fname>
        <lname>Piatos</lname>
        <age>0</age>
        <code>1002</code> 
        <hobby>reading</hobby>
        <hobby>sports</hobby>
        <hobby>travelling</hobby>
    </player>
    <player>
        <id>2</id>
        <fname>Mayo</fname>
        <lname>Naise</lname>
        <age>22</age>
        <code>1003</code>
        <hobby>reading books</hobby>
        <hobby>program hobyist</hobby>
        <hobby>watching tutorials</hobby>
    </player>
    <player>
        <id>3</id>
        <fname>Ben</fname>
        <lname>Ten</lname>
        <age>22</age>
        <code>1004</code>
        <hobby>play sports</hobby>
    </player>
    <player>
        <id>4</id>
        <fname>Dummy</fname>
        <lname>One</lname>
        <age>22</age>
        <code>1005</code>
        <hobby>Eat</hobby>
    </player>
    <player>
        <id>5</id>
        <fname>Dummy</fname>
        <lname>Two</lname>
        <age>23</age>
        <code>1006</code>
        <hobby>sleep</hobby>
    </player>
</players>

Expected Output:

File1.xml

<players>
  <player>
      <id>3</id>
      <fname>Ben</fname>
      <lname>Ten</lname>
      <age>22</age>
      <code>1004</code>
      <hobby>play sports</hobby>
  </player>
  <player>
      <id>4</id>
      <fname>Dummy</fname>
      <lname>One</lname>
      <age>22</age>
      <code>1005</code>
      <hobby>Eat</hobby>
  </player>
  <player>
      <id>5</id>
      <fname>Dummy</fname>
      <lname>Two</lname>
      <age>23</age>
      <code>1006</code>
      <hobby>sleep</hobby>
  </player>
</players>

file2.xml multiple hobby

<?xml version="1.0" encoding="UTF-8"?>
<players>
  <player>
      <id>1</id>
      <fname>Vcut</fname>
      <lname>Piatos</lname>
      <age>0</age>
      <code>1002</code>
      <hobby>reading</hobby>
  </player>
  <player>
      <id>1</id>
      <fname>Vcut</fname>
      <lname>Piatos</lname>
      <age>0</age>
      <code>1002</code>
      <hobby>sports</hobby>
  </player>
  <player>
      <id>1</id>
      <fname>Vcut</fname>
      <lname>Piatos</lname>
      <age>0</age>
      <code>1002</code>
      <hobby>travelling</hobby>
  </player>
  <player>
      <id>2</id>
      <fname>Mayo</fname>
      <lname>Naise</lname>
      <age>22</age>
      <code>1003</code>
      <hobby>reading books</hobby>        
  </player>
      <player>
      <id>2</id>
      <fname>Mayo</fname>
      <lname>Naise</lname>
      <age>22</age>
      <code>1003</code>       
      <hobby>program hobyist</hobby>      
  </player>
      <player>
      <id>2</id>
      <fname>Mayo</fname>
      <lname>Naise</lname>
      <age>22</age>
      <code>1003</code>       
      <hobby>watching tutorials</hobby>
  </player>
</players>

Tried applying same solution provided here but wasn't able to make it work.

Any tips you can provide? Idea I have is to count the hobby per player but not really sure how to split it and copy other segments after..

<xsl:if count(player/hobby> > 1 )

Actual XSLT that Im using ( literally a copy from the solution referenced I've shared )

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
    
    <xsl:output indent="yes"/>
   
    <xsl:template match="/">
        <xsl:apply-templates select="*/player"/>
    </xsl:template>
    
    <xsl:template match="*/player">
        <xsl:result-document href="Output{position()}.xml">
            <xsl:apply-templates select="/*" mode="split">
                <xsl:with-param name="target" tunnel="yes" select="current()"/>
            </xsl:apply-templates>
        </xsl:result-document>
    </xsl:template>
    
    <xsl:template match="@* | node()" mode="#all">
        <xsl:copy>
            <xsl:apply-templates select="@*, node()" mode="#current"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="*/player" mode="split">
        <xsl:param name="target" tunnel="yes"/>
        <xsl:if test="$target is .">
            <xsl:next-match/>
        </xsl:if>
    </xsl:template>
    
    
</xsl:stylesheet>

This produces 5 file (1 per player) , im playing with it a bit but wasn't able to group player with single hobby into single file and for the multiple hobby its more worse.

Will really appreciate if you can share more direction.

Grimmjow
  • 45
  • 10
  • what sort of errors did you encounter? XSLT 1 or XSLT 2? What xslt engine/tech stack are you using? – bknights Aug 26 '20 at 20:00
  • 1
    Why don't you post your attempt so we can fix it, instead of having to write your code for you from scratch. – michael.hor257k Aug 26 '20 at 20:31
  • So the first output file contains all `player`s with only a single `hobby` child element. The second, I suppose, all `player`s with more than one `hobby`. However, it is not clear why the `player` with `id` being `1` appears three times in the second output. If you wanted one `player` result there for each `hobby` shouldn't `player` `1` appear five times? – Martin Honnen Aug 26 '20 at 20:33
  • Sorry, i will post all trials here and revert back. – Grimmjow Aug 26 '20 at 20:38
  • Bknights , sorry for the late answer. Using XSLT, not really sure what you meant for the engine/tech stack, just using oxygen to run it, not familiar with those. michael.hor257k, I reupdated the thread with trial I've been doing. @MartinHonnen, I've re-updated it properly, wrong copy and paste of examples. Let me know if you'll need more info,thanks – Grimmjow Aug 27 '20 at 15:49

1 Answers1

1

AFAICT, you could do simply:

XSLT 2.0

<xsl:stylesheet version="2.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="/players">
    <xsl:for-each-group select="player" group-by="count(hobby) gt 1 ">
        <result-document href="Output{position()}.xml">
            <players>
                <xsl:apply-templates select="current-group()/hobby"/>
            </players>
        </result-document>
    </xsl:for-each-group>
</xsl:template>

<xsl:template match="hobby">
    <player>
        <xsl:copy-of select="../* except ../hobby, ."/>
    </player>
</xsl:template>
 
</xsl:stylesheet>
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • This is working, thanks a lot but I need to understand everything on my own before I used it blindly. Big thanks! – Grimmjow Aug 27 '20 at 16:47
  • 1
    *"I need to understand everything on my own"* I strongly encourage you to do so. If you need any clarifications, ask. – michael.hor257k Aug 27 '20 at 16:53