使用xml_split根据Perl正则表达式或XPath表达式拆分XML文件

时间:2015-12-31 20:52:09

标签: xml perl xpath

我有一个巨大的XML文件,我想根据产品类型属性拆分成块。

我不知道如何使用XSLT。我发现xml_split但无法弄清楚如何使用正则表达式或XPath来根据类型属性拆分文档

1*500+2*400+1*200=1500

我使用了这个命令

<?xml version="1.0"?>
<!DOCTYPE catalog SYSTEM "catalog.dtd">
<catalog>
   <product type="cloths" product_image="cardigan.jpg">
      <catalog_item gender="Men's">
         <item_number>QWZ5671</item_number>
         <price>39.95</price>
         <size description="Medium">
            <color_swatch image="red_cardigan.jpg">Red</color_swatch>
            <color_swatch image="burgundy_cardigan.jpg">Burgundy</color_swatch>
         </size>
         <size description="Large">
            <color_swatch image="red_cardigan.jpg">Red</color_swatch>
            <color_swatch image="burgundy_cardigan.jpg">Burgundy</color_swatch>
         </size>
      </catalog_item>
      <catalog_item gender="Women's">
         <item_number>RRX9856</item_number>
         <price>42.50</price>
         <size description="Small">
            <color_swatch image="red_cardigan.jpg">Red</color_swatch>
            <color_swatch image="navy_cardigan.jpg">Navy</color_swatch>
            <color_swatch image="burgundy_cardigan.jpg">Burgundy</color_swatch>
         </size>
         <size description="Medium">
            <color_swatch image="red_cardigan.jpg">Red</color_swatch>
            <color_swatch image="navy_cardigan.jpg">Navy</color_swatch>
            <color_swatch image="burgundy_cardigan.jpg">Burgundy</color_swatch>
            <color_swatch image="black_cardigan.jpg">Black</color_swatch>
         </size>
         <size description="Large">
            <color_swatch image="navy_cardigan.jpg">Navy</color_swatch>
            <color_swatch image="black_cardigan.jpg">Black</color_swatch>
         </size>
         <size description="Extra Large">
            <color_swatch image="burgundy_cardigan.jpg">Burgundy</color_swatch>
            <color_swatch image="black_cardigan.jpg">Black</color_swatch>
         </size>
      </catalog_item>
   </product>
</catalog>

但它会在没有XPath过滤的情况下重现完整的XML文档。

1 个答案:

答案 0 :(得分:2)

好的,所以 - 如果我看对了你,你就是在考虑将你的产品类型分成不同的文件。

我可能会这样做,使用XML::Twig

#!/usr/bin/env perl
use strict;
use warnings;

use XML::Twig;

sub split_product {
    my ( $twig, $product ) = @_;
    open( my $output, '>>', $product->att('type') . ".xml" ) or warn $!;
    print {$output} $product->sprint;
    $twig -> purge; 
}

my $twig = XML::Twig->new(
    pretty_print  => 'indented_a',
    twig_handlers => { 'product' => \&split_product }
);
$twig->parsefile('source.xml');

虽然这不会保留XML结构,但它只是放置了产品&#39;元素到一个新文件。 (如果有相同类型的多个产品,那么它将不再是有效的XML。)

好的,所以给定每种类型的多个产品,遍历文件是必要的。这使它变得更加复杂,因为你无法关闭&#39;你的XML,直到你知道它需要什么,这意味着你需要两次遍历你的树。

解决此问题的更简单(内存密集)方式是:

#!/usr/bin/env perl
use strict;
use warnings;

my %products;

use XML::Twig;

sub split_product {
    my ( $twig, $product ) = @_;
    my $type = $product->att('type');
    if ( not $products{$type} ) {
        my $new_product = XML::Twig->new;
        $new_product->set_root( XML::Twig::Elt->new('catalogue') );
        $new_product->set_xml_version('1.0');
        $new_product->set_encoding('utf-8');
        $new_product->set_doctype('catalog SYSTEM "catalog.dtd"');
        $products{$type} = $new_product;
    }
    $product->cut;
    $product->paste( 'last_child', $products{$type}->root );
    $twig->purge;
}

my $twig = XML::Twig->new(
    pretty_print  => 'indented_a',
    twig_handlers => { 'product' => \&split_product }
);
$twig->parsefile ( 'your_file.xml' );

foreach my $product_type ( keys %products ) {
    open ( my $output, '>', "$product_type.xml" ) or warn $!; 
    print {$output} $products{$product_type}->sprint;
}

这会将其分解为单独的有效文档,但要注意 - 它将占用内存中XML大小的10倍。

最后,但并非最不重要 - 一个(希望!)内存密集度较低的版本,使用flushpurge来转储已解析的XML。

#!/usr/bin/env perl
use strict;
use warnings;

my %products;
my %product_files;

use XML::Twig;

sub split_product {
    my ( $twig, $product ) = @_;
    my $type = $product->att('type');
    if ( not $products{$type} ) {
        my $new_product = XML::Twig->new;
        $new_product->set_root( XML::Twig::Elt->new('catalogue') );
        $new_product->set_xml_version('1.0');
        $new_product->set_encoding('utf-8');
        $new_product->set_doctype('catalog SYSTEM "catalog.dtd"');
        $products{$type} = $new_product;
        open( $product_files{$type}, '>', "$type.xml" ) or warn $!;
    }
    $product->cut;
    $product->paste( 'last_child', $products{$type}->root );
    $twig->purge;
    $products{$type}->flush( $product_files{$type} );
}

my $twig = XML::Twig->new(
    pretty_print  => 'indented_a',
    twig_handlers => { 'product' => \&split_product }
);
$twig->parsefile ( 'your_file.xml' );

foreach my $product_type ( keys %products ) {
    $products{$product_type}->flush( $product_files{$product_type} );
    close( $product_files{$product_type} );
}

如果您只想选择一种特定类型,我们可以在脚本中设置它:

my $target_type = 'cloths'; 

或者从@ARGV(命令行参数)中读取它。

my ( $target_type ) = @ARGV; 

然后设置你的twig_handler&#39;到:

"product[\@type=\"$target_type\"]" => \&split_product

虽然这意味着从内存中清除数据的频率较低。所以你可以添加到处理程序中:

if ( $product -> att('type') eq $target_type ) { 
    $twig -> purge;
    return;
}