Moose has a very nice method modifier called augment that allows one to use OO inheritance to separate general and specific XML rendering. For instance, if all of your XML must have certain outer wrapper tags, then your base class method can build that XML and call inner(). Using XML::Generator your base class would be:
QBXML( QBXMLMsgsRq( {onError => "stopOnError"}, inner() ));
And a specific inner class to build inner XML might be:
augment 'as_xml' => sub { my ($self, $name)=@_; VendorAddRq( VendorAdd( Name($name))); };
Well, that's a very nice start on taking the directed-graph nature of XML and layering it on the directed-graph nature of class-based oo inheritance.
Ok, so what happens when there are numerous optional, nested child nodes? For instance, the code above generates the general wrapper and specific code for the required fields of a Quickbooks VendorAdd request:
</CODE><?xml version="1.0" encoding="utf-8"?> <?qbxml version="10.0"?> <QBXML> <QBXMLMsgsRq onError="stopOnError"> <VendorAddRq> <VendorAdd> <!-- required --> <Name >STRTYPE</Name> <!-- required --> </VendorAdd> </VendorAddRq> </QBXMLMsgsRq> </QBXML>
but as you can see, there are numerous optional nodes that are children or grandchildren of the VendorAdd element.
<?xml version="1.0" encoding="utf-8"?> <?qbxml version="10.0"?> <QBXML> <QBXMLMsgsRq onError="stopOnError"> <VendorAddRq> <VendorAdd> <!-- required --> <Name >STRTYPE</Name> <!-- required --> <IsActive >BOOLTYPE</IsActive> <!-- optional --> <CompanyName >STRTYPE</CompanyName> <!-- optional --> <Salutation >STRTYPE</Salutation> <!-- optional --> <FirstName >STRTYPE</FirstName> <!-- optional --> <MiddleName >STRTYPE</MiddleName> <!-- optional --> <LastName >STRTYPE</LastName> <!-- optional --> <VendorAddress> <!-- optional --> <Addr1 >STRTYPE</Addr1> <!-- optional --> <Addr2 >STRTYPE</Addr2> <!-- optional --> <Addr3 >STRTYPE</Addr3> <!-- optional --> <Addr4 >STRTYPE</Addr4> <!-- optional --> <Addr5 >STRTYPE</Addr5> <!-- optional --> <City >STRTYPE</City> <!-- optional --> <State >STRTYPE</State> <!-- optional --> <PostalCode >STRTYPE</PostalCode> <!-- optional --> </VendorAddress> </VendorAdd> </VendorAddRq> </QBXMLMsgsRq> </QBXML>
How do you handle conditional, nested generation of XML using object-oriented mechanisms? As I write this, it seems the best way is for these various children to be various Roles (Moose::Role) which you can can call to build the various children, all of which know where in the tree to place themselves, and only place themselves there if they were constructed with renderable data. Ie, if they were null, then they simply dont render.
Practically, if you pull a row from the database, then you want to throw that row at a series of constructors, heedless of whether the values or defined or not and only have the instances render in the tree is the particular data column were defined
So, any conditional rendering based on data would require tortuous conditionals.
And then for optional nested nodes, we would have nested code which would optionally create objects:{ my $VendorAddRq = XML::Writer::Nest->new(tag => 'VendorAddRq'); { my $VendorAdd = $VendorAddRq->nest('VendorAdd'); { my $Name = $VendorAdd->data(Name => $name) } } }
{ my $VendorAdd = $VendorAddRq->nest('VendorAdd'); { my $Name = $VendorAdd->data(Name => $name) } { my $IsActive = $row->{active} ? $VendorAdd->data(IsActive => 1) + : 0 } }
What happens is the Inactive node gets rendered as a function of whether or not the incoming hashref has active set. If it's set, then $IsActive will be an object that is designed to render nested XML. Otherwise, $IsActive is a normal scalar and no XML will be rendered.
VendorAddRq( VendorAdd( Name($name), maybeRenderIsActive($row) ) )
For configurable software construction, the problem with XML::Generator and XML::Writer::Nest is separating XML construction from the boolean function to decide whether to render. In other words, each user of your nested optional XML "specification" should be able to hook in their desired subroutines for deciding which nodes should render. Thus, the above XML::Generator really should look like this:
And the programmer can override the default logic_engine with methods based on the data and business requirements. For instance, the default logic_engine might look like this:augment 'as_xml' => sub { my ($self, $name, $optionaldata)=@_; VendorAddRq( VendorAdd( Name($name), $self->logic_engine->maybeRenderIsActive(@_) # just give + it the whole indata ) )
And then a developer has the choice of mapping his hashref to the same values as the logic engine or supplying his own logic engine.package XML::Quickbooks::LogicEngine; sub IsActive { my ($self, $name, $databaserow)=@_; $databaserow->{active}; } 1;
-- Terence Parr, "Enforcing Strict Model View Separation in Template Engines"
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: Moose, the tree structure of XML and object-oriented inheritance hiearchies
by perigrin (Sexton) on Jun 20, 2011 at 20:26 UTC | |
|
Re: Moose, the tree structure of XML and object-oriented inheritance hiearchies
by metaperl (Curate) on Jun 20, 2011 at 21:09 UTC | |
|
Re: Moose, the tree structure of XML and object-oriented inheritance hiearchies
by locked_user sundialsvc4 (Abbot) on Jun 23, 2011 at 13:48 UTC | |
by metaperl (Curate) on Jun 27, 2011 at 14:30 UTC |