http://www.perlmonks.org?node_id=1006072


in reply to how to update an xml file by read data from csv file

Text::CSV makes reading and writing CSV files very easy. It has a simple example in the synopsis that will probably do what you need just by copying and changing the filename. That takes care of the first half of your problem.

If you're always using exactly the same input XML file you can probably get away with using a regex (regex tutorial)) to search and replace the elements that you want to change-- simply read in the file as text, make the change, and write it out.

If you start making a habit of modifying XML files, especially if they're variable and may be a little unpredictable, you'll probably want to look into XML::TreeBuilder or XML::LibXML

Try putting something together yourself, and if you run into trouble, post back in this thread and show your code and describe what it's doing wrong

Replies are listed 'Best First'.
Re^2: how to update an xml file by read data from csv file
by Vijay81 (Acolyte) on Nov 29, 2012 at 21:38 UTC

    Thanks bitingduck (Pilgrim) for you input. As you suggested i used Text::CSV module to read csv file.

    Main pl file :
    #!/usr/bin/perl use warnings; use strict; use warnings; use Text::CSV; my $file1 = 'C:\TR_Vijay\Automation\serverinput.csv'; my $csv = Text::CSV->new(); my @columns; open (CSV, "<", $file1) or die $!; while (<CSV>) { next if ($. == 0); if ($csv->parse($_)) { @columns = $csv->fields(); print "$columns[0] \n"; print "$columns[1] \n"; print "$columns[2] \n"; print "$columns[3] \n"; } else { my $err = $csv->error_input; print "Failed to parse line: $err"; } open my $file, '>>', 'result.xml' or die "Can't open file: $!"; print $file <<EOF <?xml version="1.0" encoding="UTF-8"?> <!-- <!*************************************************************** +*> --> <!-- <!* + *> --> <!-- <!* PuTTY Configuration Manager save file - All right reserved. +*> --> <!-- <!* +*> --> <!-- <!*************************************************************** +*> --> <!-- <!The following lines can be modified at your own risks.> --> <configuration version=$columns[0] savepassword="True"> <root type="database" name=$columns[1] expanded="True"> <container type="folder" name=$columns[2] expanded="True"> <container type="folder" name=$columns[3] expanded="True"> <connection type="PuTTY" name=$columns[4]> <connection_info> <name>$columns[4]</name> <protocol>SSH</protocol> <host>$columns[7]</host> <port>$columns[8]</port> <session>Default Settings</session> <commandline>$columns[10]</commandline> <description /> </connection_info> <login> </login> <timeout> <connectiontimeout>1000</connectiontimeout> <logintimeout>750</logintimeout> <passwordtimeout>750</passwordtimeout> <commandtimeout>750</commandtimeout> </timeout> <command> </command> <options> <loginmacro>False</loginmacro> <postcommands>False</postcommands> <endlinechar>10</endlinechar> </options> </connection> </root> </configuration> EOF ; } close CSV;
    I was able to achive almost expect for few things. Anyhelp would be much appreciated. Achieved so far : 1. able to read data from csv file 2. ouput the xml file with data read from csv file. Now Issues are : 1. Don't want to print first 7 lines of the xml format everytime. It should be printed only once at top of the file.
    <?xml version="1.0" encoding="UTF-8"?> <!-- <!*************************************************************** +*> --> <!-- <!* + *> --> <!-- <!* PuTTY Configuration Manager save file - All right reserved. +*> --> <!-- <!* +*> --> <!-- <!*************************************************************** +*> --> <!-- <!The following lines can be modified at your own risks.> -->
    2. Don't want to print last 2 line everytime. it just need to be printed only once at bottom of the file
    </root> </configuration>
    3. Not sure where i made mistake, but when ever i try to open the result.xml, it doesn't open. even program doesn't give any error thou.
      If you do not want to print the lines every time, move the corresponding print out of the loop.
      لսႽ† ᥲᥒ⚪⟊Ⴙᘓᖇ Ꮅᘓᖇ⎱ Ⴙᥲ𝇋ƙᘓᖇ

      Here's a solution that uses XML::TreeBuilder (which uses XML::Element methods as well). It seemed easier than trying to build it up from print statements and regex replacements.

      One maybe major caveat: your example input file isn't valid XML-- the two "container" tags don't get closed. I took the liberty of assuming (maybe incorrectly) that they should get closed and that you want valid XML.

      Here's the code:
      #!/usr/bin/perl use warnings; use strict; use Text::CSV; use XML::TreeBuilder; use Data::Dumper; my $xmlbase='original.xml'; my $paramsfile = 'params.csv'; # the list of tags filled from the csv file my @taglist=qw(name protocol host commandline); # this is the part that repeats, so we break it out separately # the tags that get filled from the CSV file are left empty my $connection= <<END <connection type="PuTTY" name="WSSS"> <connection_info> <name></name> <protocol></protocol> <host></host> <port>22</port> <session>Default Settings</session> <commandline></commandline> <description /> </connection_info> <login> <login /> <password /> <prompt /> </login> <timeout> <connectiontimeout>1000</connectiontimeout> <logintimeout>750</logintimeout> <passwordtimeout>750</passwordtimeout> <commandtimeout>750</commandtimeout> </timeout> <command> <command1 /> <command2 /> <command3 /> <command4 /> <command5 /> </command> <options> <loginmacro>False</loginmacro> <postcommands>False</postcommands> <endlinechar>10</endlinechar> </options> </connection> END ; #this is the wrapper my $config= <<END <?xml version="1.0" encoding="UTF-8"?> <!-- The following lines can be modified at your own risks. --> <configuration version="0.2.1.4" savepassword="True"> <root type="database" name="WSSS" expanded="True"> <container type="folder" name="CM" expanded="True"> <container type="folder" name="1-AD/M" expanded="True"> </container> </container> </root> </configuration> END ; #start an xml structure with the outer wrapper my $tree=XML::TreeBuilder->new(); $tree->parse($config); # find the insert location my $container=$tree->look_down("_tag"=>"container", "name"=>"1-AD/M"); + my $csv=Text::CSV->new(); open my $fh, "<:encoding(utf8)", $paramsfile or die "$paramsfile: $!"; # loop through the lines of the CSV and copy the connection structure +into # the wrapper with the updated values while(my $row=$csv->getline($fh)){ my $contree=XML::TreeBuilder->new(); #make an xml structure of th +e connection $contree->parse($connection); $container->push_content($contree); #insert it into the wrapper $contree->attr('name',$row->[0]); #set the name attribute $container->push_content("\n "); #make it a little prettier my $index=0; for my $tagname(@taglist){ #loop through the columns my $tag=$contree->look_down('_tag'=> $tagname); $tag->push_content($row->[$index++]); } } print '<?xml version="1.0" encoding="UTF-8"?>'."\n"; print $tree->as_XML();
      And here's the output:
      $ perl puttyconfig.pl <?xml version="1.0" encoding="UTF-8"?> <configuration savepassword="True" version="0.2.1.4"> <root expanded="True" name="WSSS" type="database"> <container expanded="True" name="CM" type="folder"> <container expanded="True" name="1-AD/M" type="folder"> <connection name="WSSSS" type="PuTTY"> <connection_info> <name>WSSSS</name> <protocol>SSH</protocol> <host>103.243.543.233</host> <port>22</port> <session>Default Settings</session> <commandline>erted</commandline> <description></description> </connection_info> <login> <login></login> <password></password> <prompt></prompt> </login> <timeout> <connectiontimeout>1000</connectiontimeout> <logintimeout>750</logintimeout> <passwordtimeout>750</passwordtimeout> <commandtimeout>750</commandtimeout> </timeout> <command> <command1></command1> <command2></command2> <command3></command3> <command4></command4> <command5></command5> </command> <options> <loginmacro>False</loginmacro> <postcommands>False</postcommands> <endlinechar>10</endlinechar> </options> </connection> <connection name="AWSA" type="PuTTY"> <connection_info> <name>AWSA</name> <protocol>SSH</protocol> <host>303.233.542.622</host> <port>22</port> <session>Default Settings</session> <commandline>rrertd</commandline> <description></description> </connection_info> <login> <login></login> <password></password> <prompt></prompt> </login> <timeout> <connectiontimeout>1000</connectiontimeout> <logintimeout>750</logintimeout> <passwordtimeout>750</passwordtimeout> <commandtimeout>750</commandtimeout> </timeout> <command> <command1></command1> <command2></command2> <command3></command3> <command4></command4> <command5></command5> </command> <options> <loginmacro>False</loginmacro> <postcommands>False</postcommands> <endlinechar>10</endlinechar> </options> </connection> </container> </container> </root> </configuration>

        Thanks bitingduck (Pilgrim). Your suggested solution is very clear and neat way of producing xml format file. But only problem is that i don't want to display last 2 closing container tags. I did try removing that from wrapper but it didn't work. Is ther any way we can't avoid writing the last 2 container tags please..

        Error we get if we remove the closing tag :
        mismatched tag at line 7, column 4, byte 333 at C:/Perl64/lib/XML/Pars +er.pm line 187.
        Main program :
        #!/usr/bin/perl use warnings; use strict; use Text::CSV; use XML::TreeBuilder; use Data::Dumper; my $xmlbase='original.xml'; my $paramsfile = 'params.csv'; # the list of tags filled from the csv file my @taglist=qw(name protocol host commandline); # this is the part that repeats, so we break it out separately # the tags that get filled from the CSV file are left empty my $connection= <<END <connection type="PuTTY" name="WSSS"> <connection_info> <name></name> <protocol></protocol> <host></host> <port>22</port> <session>Default Settings</session> <commandline></commandline> <description /> </connection_info> <login> <login /> <password /> <prompt /> </login> <timeout> <connectiontimeout>1000</connectiontimeout> <logintimeout>750</logintimeout> <passwordtimeout>750</passwordtimeout> <commandtimeout>750</commandtimeout> </timeout> <command> <command1 /> <command2 /> <command3 /> <command4 /> <command5 /> </command> <options> <loginmacro>False</loginmacro> <postcommands>False</postcommands> <endlinechar>10</endlinechar> </options> </connection> END ; open (my $resultfile, '>>', 'resultxml.xml') or die "Can't open file: +$!"; #this is the wrapper my $config= <<END <?xml version="1.0" encoding="UTF-8"?> <!-- The following lines can be modified at your own risks. --> <configuration version="0.2.1.4" savepassword="True"> <root type="database" name="WSSS" expanded="True"> <container type="folder" name="CM" expanded="True"> <container type="folder" name="1-AD/M" expanded="True"> </container> </container> </root> </configuration> END ; #start an xml structure with the outer wrapper my $tree=XML::TreeBuilder->new(); $tree->parse($config); # find the insert location my $container=$tree->look_down("_tag"=>"container", "name"=>"1-AD/M"); my $csv=Text::CSV->new(); open my $fh, "<:encoding(utf8)", $paramsfile or die "$paramsfile: $!"; # loop through the lines of the CSV and copy the connection structure +into # the wrapper with the updated values while(my $row=$csv->getline($fh)){ my $contree=XML::TreeBuilder->new(); #make an xml structure of th +e connection $contree->parse($connection); $container->push_content($contree); #insert it into the wrapper $contree->attr('name',$row->[0]); #set the name attribute $container->push_content("\n "); #make it a little prettier my $index=0; for my $tagname(@taglist){ #loop through the columns my $tag=$contree->look_down('_tag'=> $tagname); $tag->push_content($row->[$index++]); } } #print '<?xml version="1.0" encoding="UTF-8"?>'."\n"; print $tree->as_XML(); print $resultfile '<?xml version="1.0" encoding="UTF-8"?>'."\n"; print $resultfile $tree->as_XML(); close($resultfile);