Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks,

I am writing some OO classes whose properties will be set based on the contents of XML files. When creating a new instance of the OO class, the user passes an $id. Based on the value of $id, a certain XML file will need to be parsed and the properties of the new instance need to be set from the XML file.

I'm using XML::Parser to do the parsing. How can I set the properties of the new $self object out of the startElement sub that is doing the parsing? Here is what I mean:

package Person; use XML::Parser; sub new { my $class = shift; my $id = shift; my $self = { id => $id }; bless $self; ## now, set up XML::Parser; $parser->parsefile ("/path/to/some/file/$id.xml"); return $self; } # standard XML::Parser stuff here sub startElement { my( $parseinst, $element, %attrs ) = @_; if ($element eq "TAG") { my $property = $attrs{Property}; ## HOW TO SET $self->{property} from here? } }

Or is XML::Parser the wrong tool for this job?

Thanks!

Replies are listed 'Best First'.
Re: Create OO object from XML
by kcott (Archbishop) on Mar 22, 2012 at 02:42 UTC

    You've got quite a few problems here: $class not used in new(); $parser leaps into existence in new(); $parser->parsefile(...); seems to be in totally the wrong place; no connection between new() and startElement(); and others.

    Without hacking your code around too much, I'd probably change:

    bless $self; ## now, set up XML::Parser; $parser->parsefile ("/path/to/some/file/$id.xml"); return $self;
    to
    bless $self, $class; $self->init(); return $self;

    with init() looking something like:

    sub init { my $self = shift; my $id = $self->{id}; # Parse the "$id.xml" file here # Get the $property value $self->{property} = $property; return; }

    You'd be well advised to put use strict; and use warnings; at the top of your code.

    -- Ken

Re: Create OO object from XML
by GrandFather (Saint) on Mar 22, 2012 at 02:38 UTC

    I'd use XML::Twig for that sort of task. Regardless, you'd use the same trick to get the data into your object. You don't show the code that sets up the parser and associates startElement with the start element event, but where you do that at present you probably have something like:

    my $parser = XML::Parser->new(Handlers => {Start => \&startElement});

    You can change that to:

    my $parser = XML::Parser->new( Handlers => { Start => sub {$self->startElement(@_);} } );

    then startElement becomes a member:

    sub startElement { my ($self, $parseinst, $element, %attrs ) = @_;

    and a complete sample would look like:

    use strict; use warnings; use XML::Parser; sub new { my ($class, %params) = @_; my $self = bless \%params, $class; my $parser = XML::Parser->new( Handlers => { Start => sub {$self->startElement(@_);} } ); $parser->parse(*DATA); return $self; } sub startElement { my ($self, $parseinst, $element, %attrs ) = @_; return if $element ne $self->{id}; $self->{Property} = $attrs{Property}; } my $obj = main->new(id => 'child'); print $obj->{Property}; __DATA__ <root> <child Property='Hello World'/> </root>
    True laziness is hard work
Re: Create OO object from XML
by Jenda (Abbot) on Mar 22, 2012 at 08:35 UTC

    You could use XML::Rules and set the rules so that the $parser->parsefile() returns a reference you can bless into your class and be done. And all that even if the XML is rather complex and the object you are building contains references to other objects filled in from the XML.

    Jenda
    Enoch was right!
    Enjoy the last years of Rome.

Re: Create OO object from XML
by jeffa (Bishop) on Mar 22, 2012 at 16:00 UTC
Re: Create OO object from XML
by tobyink (Canon) on Mar 22, 2012 at 09:09 UTC

    This is still in a fairly early stage of development, but take a look at the module XML::LibXML::Augment which I'm developing. (Not on CPAN yet I'm afraid.)

    The idea is that XML::LibXML already provides a nice object-oriented view of the data, but we can specialise each object to make it even better. So say we have some XML:

    <employees xmlns="http://megacorp.example/"> <person> <name>Alice</name> <salary>40000</salary> <bank_account number="10001234"/> </person> <person> <name>Bob</name> <salary>37500</salary> <bank_account number="10005678"/> </person> </employees>

    Normally the two <person> elements would be XML::LibXML::Element objects. With XML::LibXML::Augment, we create a subclass of XML::LibXML::Element and bind it to the namespace-qualified name {http://megacorp.example/}person. And then whenever we hit a <person> element in our document, our subclass is automatically used.

    So we might write:

    { package MegaCorp::Person; use XML::LibXML::Augment -names => ['{http://megacorp.example/}person']; sub get_salary { shift -> getElementsByTagName('salary') -> get_node(1) # not zero-indexed! -> textContent; } sub get_bank_account { shift -> getElementsByTagName('bank_account') -> get_node(1); } sub pay_salary { my $self = shift; my $acct = $self->get_bank_account; $acct->accept_funds($self->get_salary); } } { package MegaCorp::BankAccount; use XML::LibXML::Augment -names => ['{http://megacorp.example/}bank_account']; sub accept_funds { ... } } { package main; use XML::LibXML::Augment; my $doc = XML::LibXML->load_xml(location => 'emp.xml'); # Switch on augmentation for this document! XML::LibXML::Augment->upgrade($doc); my @employees = $doc->getElementsByTagName('person'); $_->pay_salary for @employees; }

    If you think you'd like to give it a whirl, do get in touch with me! I'd love to have some extra people playing with it and reporting bugs and ideas for improvements.

    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'