Ti2 Ti2 - 4 months ago 12
Perl Question

Changing a field in an XML document



I have an XML file that has this structure:

<user-mapping>
<authorize username="user2" password="asdasdsss" >
<connection name="U2 - Windows 7 ">
<protocol>rdp</protocol>
<param name="hostname">192.168.203.2</param>
<param name="port">21122</param>
<param name="password">asdasdsss</param>
<param name="username">user</param>
</connection>
<connection name="U2 - Windows XP SP3 ">
<protocol>rdp</protocol>
<param name="hostname">192.168.203.2</param>
<param name="port">21120</param>
<param name="password">asdasdsss</param>
<param name="username">user</param>
</connection>
</authorize>
</user-mapping>


This file has around 20 users (
authorize
elements) and each user has around 15 machine connections.

I have created a Perl program to change only the password field, but it is based on counting lines and characters until the field is found. If I add additional
param
elements then the script has to be modified.

I want to have a dynamic way of doingit, so that Perl can select the correct user node and change the password field in all related machines. I tried many ways but I couldn't get it to work.

The password should be changed for the user and for all machines. They will all will be changed to the same password

My code which is used now:

open( FILL, "< /etc/user-mapping.xml" );

@strings = <FILL>;
$i = 0;

foreach $line (@strings) {
print $i;
print $line;
$i++;
}

print "Number: ";
print $set_id;

$set_id = $set_id - 2;
$set_id = $set_id * 102;

print "Line: ";
print $set_id;

substr( @strings[ 1 + $set_id ], 43, 9 ) = "$password";
substr( @strings[ 6 + $set_id ], 39, 9 ) = "$password";
substr( @strings[ 13 + $set_id ], 39, 9 ) = "$password";

foreach $line ( @strings ) {
print $i;
print $line;
$i++;
}

open( FH, "> /etc/user-mapping.xml" );
foreach $line (@strings) {
print( FH $line );
}

close(FH);

close(FILL);

Answer

I hope you understand that keeping passwords as plain text is a very bad idea?

You must use a proper XML parser, and I suggest that you use XML::Twig as it is a little friendlier than some alternatives

Here's a program that does as you ask. It changes the password to abc123 throughout. As you can see, it's even rather shorter than your original program

It uses XPath expressions to find all the /user-mapping/authorize elements in the document and changes their password attributes to the new password. Then it finds every connection/param element within each authorize element that has a name="password" attribute, and changes the text value of those too

use strict;
use warnings 'all';

use XML::Twig;

use constant XML_FILE     => 'user-mapping.xml';
use constant NEW_PASSWORD => 'abc123';
use constant USER_NAME    => 'user2';

my $twig = XML::Twig->new;
$twig->parsefile(XML_FILE);

for my $auth ( $twig->findnodes('/user-mapping/authorize') ) {

    next unless $auth->att('username') eq USER_NAME;

    $auth->set_att(password => NEW_PASSWORD);

    for my $pass ( $auth->findnodes('connection/param[@name="password"]') ) {

        $pass->set_text(NEW_PASSWORD);

    }    
}

$twig->set_pretty_print('indented');
$twig->print;

output

<user-mapping>
  <authorize password="abc123" username="user2">
    <connection name="U2 - Windows 7 ">
      <protocol>rdp</protocol>
      <param name="hostname">192.168.203.2</param>
      <param name="port">21122</param>
      <param name="password">abc123</param>
      <param name="username">user</param>
    </connection>
    <connection name="U2 - Windows XP SP3 ">
      <protocol>rdp</protocol>
      <param name="hostname">192.168.203.2</param>
      <param name="port">21120</param>
      <param name="password">abc123</param>
      <param name="username">user</param>
    </connection>
  </authorize>
</user-mapping>