Main menu:

Site search

Categories

Tags

intern

Oracle und SQL

Perl

XML Generation in Perl – how it should have always been

At first big thanks to Mark Overmeer for XML::Compile. I had the pleasure to meet Mark in .nl once – cheers and carry on the great work.

Whenever I had to create XML (either with perl or php) I did it one way or the other with some sort of templating toolkit. In php I use(d) http://www.tinybutstrong.com/ for creating xml . In perl several templating systems come into mind like

  • tt2
  • Template::Declare
  • and many others more
  • Feeling Wrong

    this always felt wrong and awkward for several reasons:

  • You need to build/have the xml before (and creating xml from xsd is not one of my many hobbies)
  • You/the templating system have to take care whether or not a whole tree needs to be displayed and so on
  • The Rescue: XML::Compile

    This nifty Module comes to the rescue. Using XML::Compile make xml+xsd behave like I always wanted to.

    I want to demonstrate this using epp as an example. I want to create a valid epp frame for creating a contact object.

    The epp schemas may be obtained by a simple internet search eg here.

    You may create a valid epp frame by reading the both the contact-1.0.xsd and the epp-1.0.xsd each approx. 400 lines of thrilling xml. Or use some code like:

    1. Convert xsd to perl hash

    use strict;
    use warnings;
    
    use XML::Compile::Schema;
    
    my $schema = XML::Compile::Schema->new([
         'epp-xsd/epp-1.0.xsd',
         'epp-xsd/eppcom-1.0.xsd',
         'epp-xsd/contact-1.0.xsd',
                          ]);
    
    my $s = $schema->template('PERL' => '{urn:ietf:params:xml:ns:contact-1.0}create');
    print $s;
    

    which gives you a more readable idea what your data should look like:

    # is a x0:createType
    { # sequence of id, postalInfo, voice, fax, email, authInfo, disclose
    
      # is a xs:token
      # length <= 16
      # length >= 3
      id => "token",
    
      # is a x0:postalInfoType
      # occurs 1 <= # <= 2 times
      postalInfo =>
      [ { # sequence of name, org, addr
    
          # is a xs:normalizedString
          # length <= 255
          # length >= 1
          name => "example",
    ....
    

    Using this perl hash template you create the first part of your epp-xml

    2. Use perl hash to xml conversion

    use strict;
    use warnings;
    
    use XML::Compile::Schema;
    
    my $schema = XML::Compile::Schema->new([
         'epp-xsd/epp-1.0.xsd', 
         'epp-xsd/eppcom-1.0.xsd', 
         'epp-xsd/contact-1.0.xsd'
       ]);
    
    my $write  = $schema->compile(WRITER => '{urn:ietf:params:xml:ns:contact-1.0}create');
    my $doc    = XML::LibXML::Document->new('1.0', 'UTF-8');
    my $hash = {
                  id => 'idid',
                  postalInfo => {
                    'name' => 'name', 
                    'addr' => {
                        'street' => ['street', 'street2'],
                        'city'   => 'city',
                        'cc'     => 'cc',
                    },
                    type => 'int',
                  },
                  email => 'mymail',
                  "authInfo" => {pw => "PWauthInfo"},
               };
    my $xml    = $write->($doc, $hash);
    $doc->setDocumentElement($xml);
    
    print $doc->toString(1);
    

    this leads to the following xml

    
    
      idid
      
        name
        
          street
          street2
          city
          cc
        
      
      mymail
      
        PWauthInfo
      
    
    

    this xml has to be wrapped into an epp frame. As the epp frame uses xml any elements some “manual” work is necessary for creating the complete epp frame. In principle you start with {urn:ietf:params:xml:ns:epp-1.0}epp at step one.

    3. Generate epp-xml-frame and wrap contact-create command into it

    use strict;
    use warnings;
    
    use XML::Compile::Cache;
    use XML::Compile::Schema;
    
    my $cache = XML::Compile::Cache->new([
         'epp-xsd/eppcom-1.0.xsd', 
         'epp-xsd/epp-1.0.xsd', 
         'epp-xsd/extensions.xsd',
         'epp-xsd/contact-1.0.xsd',
       ]);
    
    my $create_contact_ns = '{urn:ietf:params:xml:ns:contact-1.0}create';
    my $epp_frame_ns = '{urn:ietf:params:xml:ns:epp-1.0}epp';
    my $prefixes = {'urn:ietf:params:xml:ns:contact-1.0' => 'contact'};
    
    $cache->declare(WRITER => [$create_contact_ns, $epp_frame_ns, ], 
           (
             prefixes => $prefixes,
             use_default_namespace => 1,
             include_namespaces => 1,         
            )        
          );
    
    $cache->compileAll;
    
    my $doc = XML::LibXML::Document->new('1.0', 'UTF-8');
    $doc->setStandalone(0);
    
    my $contact_data = {
                    id => 'idid',
                    postalInfo => {
                      'name' => 'name', 
                      'addr' => {
                          'street' => ['street', 'street2'],
                          'city'   => 'city',
                          'cc'     => 'cc',
                      },
                      type => 'int',
                    },
                    email => 'mymail',
                    "authInfo" => {pw => "PWauthInfo"},
                 };
    
    my $xml = $cache->writer($create_contact_ns)->($doc, $contact_data);
    
    my $epp_frame_data = {
    	         command => {
                  create => {                
                    '{urn:ietf:params:xml:ns:contact-1.0}create' => $xml,
                    },
                  clTRID => "token",
                 },
               };
    
    my $eppxml = $cache->writer($epp_frame_ns)->($doc, $epp_frame_data);
    
    $eppxml->setNamespace( 'http://www.w3.org/2001/XMLSchema-instance', 'xsi', 0 ); ## append additonal ns for the feelinx
    
    print $doc->toString(1) . 
          $eppxml->toString(1) ."\n";
    

    Voila, we have a valid epp frame without even touching the xml! Again Kudos to Mark Overmeer for making this possible!

    4. The result

    
    
      
        
          
            idid
            
              name
              
                street
                street2
                city
                cc
              
            
            mymail
            
              PWauthInfo
            
          
        
        token
      
    
    

    Write a comment