Search code examples
perlxml-parsingxml-twig

Copy XML value using XML::Twig


I have nested XML tags and need to have the value of ExternalId in Product XML to a ProductPageURL tag using XML::Twig.

<Products>
    <Product>
      <ExternalId>317851</ExternalId>
      <ProductPageUrl></ProductPageUrl>
    </Product>
    <Product>
      <ExternalId>316232</ExternalId>
      <ProductPageUrl></ProductPageUrl>
    </Product>
    <Product>
      <ExternalId>13472</ExternalId>
      <ProductPageUrl></ProductPageUrl>
    </Product>
</Products>

Expected result:

<Products>
    <Product>
      <ExternalId>PF317851</ExternalId>
      <ProductPageUrl>317851</ProductPageUrl>
    </Product>
    <Product>
      <ExternalId>PF316232</ExternalId>
      <ProductPageUrl>316232</ProductPageUrl>
    </Product>
    <Product>
      <ExternalId>PF13472</ExternalId>
      <ProductPageUrl>13472</ProductPageUrl>
    </Product>
    </Products>

I have using the below logic using XML::Twig:

my $twig = XML::Twig->new( 
        twig_handlers => {      
            'Product/ExternalId' => sub {
                $_->prefix( 'PF' );
            },
             'Product/ProductPageUrl' => sub {
                $_->set_text($_->get('Product/ExternalId'));
            }, 
                
        },
        pretty_print => 'indented',
    keep_encoding => 1,
    )->parsefile($xml_path_filename )->print_to_file($xml_path_filename);

Could you let me know how to make the code easier? I am not able to achieve the expected result.


Solution

  • There are 2 problems in your initial code: first I don't think get is an XML::Twig::Elt method. Then you first prefix the ExternalId text, then (once prefixed) try to use it to update ProductPageUrl. That won't work. In this case I think you're better off having a single handler, for the Product tag, in which you get the id data, then update both sub-elements.

    Here a solution, written as a test so it's easier to update if your output changes:

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    use Test::More tests => 1;
    
    use XML::Twig;
    
    # in and expected are in the DATA section, separated by 2 \n
    my( $in, $expected)= do { local $/="\n\n"; <DATA>};
    
    my $t= XML::Twig->new( twig_handlers => { Product => \&update_product },
                           keep_spaces => 1,
                         )     
                     ->parse( $in);
    
    is( $t->sprint, $expected, "one test to rule them all");
    
    sub update_product
      { my( $t, $product)= @_;
        my $id= $product->field( 'ExternalId');
        $product->first_child( 'ExternalId')->prefix( 'PF');
        $product->first_child( 'ProductPageUrl')->set_text( $id);
      }
    
    __DATA__
    <Products>
        <Product>
          <ExternalId>317851</ExternalId>
          <ProductPageUrl></ProductPageUrl>
        </Product>
        <Product>
          <ExternalId>316232</ExternalId>
          <ProductPageUrl></ProductPageUrl>
        </Product>
        <Product>
          <ExternalId>13472</ExternalId>
          <ProductPageUrl></ProductPageUrl>
        </Product>
    </Products>
    
    <Products>
        <Product>
          <ExternalId>PF317851</ExternalId>
          <ProductPageUrl>317851</ProductPageUrl>
        </Product>
        <Product>
          <ExternalId>PF316232</ExternalId>
          <ProductPageUrl>316232</ProductPageUrl>
        </Product>
        <Product>
          <ExternalId>PF13472</ExternalId>
          <ProductPageUrl>13472</ProductPageUrl>
        </Product>
    </Products>