Splitting xml file into several xml files using perl

Hi Everyone,

I'm new here and I was checking this old post:
/shell-programming-and-scripting/180669-splitting-file-into-several-smaller-files-using-perl.html
(cannot paste link because of lack of points)

I need to do something like this but understand very little of perl.

I also check that maybe xml_split should do what I want but didn't really work, only split the first file and then crashed.

what I want is the following I have file X and need to split it into different files (file names are not relevant).

this would be my file content:

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.002.03" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.002.03"><CstmrCdtTrfInitn><GrpHdr><MsgId>2504009000100320100060</MsgId></GrpHdr><PmtInf><PmtInfId>2014-10-06-09-39-23-305460</PmtInfId></PmtInf></CstmrCdtTrfInitn></Document><?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.002.03" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.002.03 pain.001.002.03.xsd"><CstmrCdtTrfInitn><GrpHdr><MsgId>WedDec0415:24:58CET2013</MsgId></GrpHdr></CstmrCdtTrfInitn></Document><?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.003.03" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.003.03 pain.001.003.03.xsd"><CstmrCdtTrfInitn><GrpHdr><MsgId>MonOct0609:38:36CEST2014</MsgId></GrpHdr></CstmrCdtTrfInitn></Document>

what I expect at the end is to have something like this:
file1

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.002.03" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.002.03"><CstmrCdtTrfInitn><GrpHdr><MsgId>2504009000100320100060</MsgId></GrpHdr><PmtInf><PmtInfId>2014-10-06-09-39-23-305460</PmtInfId></PmtInf></CstmrCdtTrfInitn></Document>

file2

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.002.03" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.002.03 pain.001.002.03.xsd"><CstmrCdtTrfInitn><GrpHdr><MsgId>WedDec0415:24:58CET2013</MsgId></GrpHdr></CstmrCdtTrfInitn></Document>

file3

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.003.03" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.003.03 pain.001.003.03.xsd"><CstmrCdtTrfInitn><GrpHdr><MsgId>MonOct0609:38:36CEST2014</MsgId></GrpHdr></CstmrCdtTrfInitn></Document>

at the moment I have only just started my code (very basic)

#!/usr/bin/perl -w
use Data::Dumper qw(Dumper);

# (1) quit unless we have the correct number of command-line args
$num_args = $#ARGV + 1;
if ($num_args < 1) {
    print "\nUsage: split.pl filename\n";
    exit;
}
$filename=$ARGV[0];

open INPUT, "<$filename";
@lines = <INPUT>;
close INPUT;

my @parts = split /<?xml version/, @lines; 

print Dumper \@parts;

I was expecting to get maybe only the each line or something like that in my output but I'm getting the following:

[root@~]$ perl split.pl split.xml
$VAR1 = [
          '16'
        ];

my split.xml file has 16 lines so I guess the split is giving me that it found 16 occurrences but I don't see any split being done. did I forget any parameter?

perldoc -f split

Splits the string EXPR into a list of strings and returns the list in list context, or the size of the list in scalar context.

In Perl is important to get familiar with the concept of context. You'll live or die by the scalar or list context.

split is for a string. You gave it a list.

my @parts = split /<?xml version/, @lines; 

This snip has the essence of what you were trying to learn.
The result will show the reason why, ultimately, it will not split as you expect in order to have separated files.

#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper qw(Dumper);

sub usage {
    print "Usage: $_[0] <filename>\n";
    exit;
}
#
# $0 is always the name of the program
#
my $filename = shift or usage $0;

# Same:
# my $filename = shift @ARGV or usage $0;

open my $fh, '<', $filename or die "$!\n";
     # change the separator to empty and read
     # the whole file as a string
     local $/;
     my @content = split /<?xml version/, <$fh>;
close $fh;

print Dumper \@content;
1 Like

hi, thanks for the feedback, I have now something to work with.

it looks going in the good direction the only thing I don't understand is why the output is has follows:


$VAR1 = [
          '<?',
          '="1.0" encoding="UTF-8"?>
<Document>...</Document><?',
          '="1.0" encoding="UTF-8"?>
<Document>...</Document><?',
          '="1.0" encoding="UTF-8"?>
<Document>...</Document><?',
          '="1.0" encoding="UTF-8"?>
<Document>...</Document><?',
          '="1.0" encoding="UTF-8"?>
<Document>...</Document><?',
          '="1.0" encoding="UTF-8"?>
<Document>...</Document><?',
          '="1.0" encoding="UTF-8"?>
<Document>...</Document><?',
          '="1.0" encoding="UTF-8"?>
<Document>...</Document><?',
          '="1.0" encoding="UTF-8"?>
<Document>...</Document><?',
          '="1.0" encoding="UTF-8"?>
<Document>...</Document><?',
          '="1.0" encoding="UTF-8"?>
<Document>...</Document><?',
          '="1.0" encoding="UTF-8"?>
<Document>...</Document><?',
          '="1.0" encoding="UTF-8"?>
<Document>...</Document><?',
          '="1.0" encoding="UTF-8"?>
<Document>...</Document><?',
          '="1.0" encoding="UTF-8"?>
<Document>...</Document>'
        ];

I probably need an escape character my string no?

because the first <? get's separated from the full thing.

ok I get the split fix, because of the escape character. (/<\?xml version/)

but how do I make the split give me back the matching part has part of the result line?
maybe is just easier if I store it in a variable and then place it later.

code:

#!/usr/bin/perl

use strict;
use warnings;
use Data::Dumper qw(Dumper);

sub usage {
    print "Usage: $_[0] <filename>\n";
    exit;
}
#
# $0 is always the name of the program
#
my $filename = shift or usage $0;

# Same:
# my $filename = shift @ARGV or usage $0;

open my $fh, '<', $filename or die "$!\n";
     # change the separator to empty and read
     # the whole file as a string
     local $/;
     my @content = split /<\?xml version/, <$fh>;
close $fh;

print Dumper \@content;

You did not post your last modified code.
split will consume that part that matches as you found out, except if you use a lookahead or lookbehind. I use that technique on this code.

#!/usr/bin/perl

use strict;
use warnings;

sub usage {
    print "Usage: $_[0] <filename>\n";
    exit;
}

my $filename = shift or usage $0;

open my $fh, '<', $filename or die "$!\n";
     # read the file as one string
     local $/;
     # split when after it finds </Document>
     my @content = split /(?<=<\/Document>)/, <$fh>;
close $fh;

# dynamically create files with content
my $suffix = 0;
for my $block (@content) {
    if($block ne "\n") {
    open $fh, '>', "$filename." . ++$suffix or die "$!\n";
            print $fh "$block\n";
    close $fh
    }
}
1 Like

thank you, this fixes my problem.

I also edited my previous post with the missing information.