我有一个XML格式的配置文件。我需要解析XML并转换为JSON。我可以用perl的XML2JSON模块转换它。但问题是,它不是维护XML元素的顺序。我严格需要元素,否则我无法配置
我的XML文件是这样的。我必须配置一个IP地址并将该IP设置为某个路由的网关。
<Config>
<ip>
<address>1.1.1.1</address>
<netmask>255.255.255.0</netmask>
</ip>
<route>
<network>20.20.20.0</network>
<netmask>55.255.255.0</netmask>
<gateway>1.1.1.1</gateway>
</route>
</Config>
这是我转换为JSON的perl代码
my $file = 'config.xml';
use Data::Dumper;
open my $fh, '<',$file or die;
$/ = undef;
my $data = <$fh>;
my $XML = $data;
my $XML2JSON = XML::XML2JSON->new();
my $Obj = $XML2JSON->xml2obj($XML);
print Dumper($Obj);
我得到的输出是,
$VAR1 = {'Config' => {'route' => {'netmask' => {'$t' => '55.255.255.0'},'gateway' => {'$t' => '1.1.1.1'},'network' => {'$t' => '20.20.20.0'}},'ip' => {'netmask' => {'$t' => '255.255.255.0'},'address' => {'$t' => '1.1.1.1'}}},'@encoding' => 'UTF-8','@version' => '1.0'};
我有一个脚本,它读取json对象并配置.. 但它失败了,因为它首先尝试将网关IP地址设置为尚未配置IP地址的路由,然后添加然后添加IP地址。
我严格要求先键入 ip ,然后路由才能正确配置而不会出错。像这样,我有许多依赖关系,其中键的顺序是必须的。
有什么方法可以解决这个问题吗?我尝试了几乎所有XML解析模块,如XML :: Simple,Twig :: XML,XML :: Parser。但没有任何帮助......
答案 0 :(得分:2)
这是我一起攻击的程序,它使用XML::Parser
来解析一些XML数据并以相同的顺序生成等效的JSON。它忽略任何属性,处理指令等,并要求每个XML元素必须包含子元素列表或文本节点。混合文本和元素将不起作用,并且 未被检查 ,除了程序将试图取消引用字符串
它旨在成为一个框架,供您根据需要进行增强,但与您在问题中显示的XML数据一样正常工作
use strict;
use warnings 'all';
use XML::Parser;
my $parser = XML::Parser->new(Handlers => {
Start => \&start_tag,
End => \&end_tag,
Char => \&text,
});
my $struct;
my @stack;
$parser->parsefile('config.xml');
print_json($struct->[1]);
sub start_tag {
my $expat = shift;
my ($tag, %attr) = @_;
my $elem = [ $tag => [] ];
if ( $struct ) {
my $content = $stack[-1][1];
push @{ $content }, $elem;
}
else {
$struct = $elem;
}
push @stack, $elem;
}
sub end_tag {
my $expat = shift;
my ($elem) = @_;
die "$elem <=> $stack[-1][0]" unless $stack[-1][0] eq $elem;
for my $content ( $stack[-1][1] ) {
$content = "@$content" unless grep ref, @$content;
}
pop @stack;
}
sub text {
my $expat = shift;
my ($string) = @_;
return unless $string =~ /\S/;
$string =~ s/\A\s+//;
$string =~ s/\s+\z//;
push @{ $stack[-1][1] }, $string;
}
sub print_json {
my ($data, $indent, $comma) = (@_, 0, '');
print "{\n";
for my $i ( 0 .. $#$data ) {
# Note that $data, $indent and $comma are overridden here
# to reflect the inner context
#
my $elem = $data->[$i];
my $comma = $i < $#$data ? ',' : '';
my ($tag, $data) = @$elem;
my $indent = $indent + 1;
printf qq{%s"%s" : }, ' ' x $indent, $tag;
if ( ref $data ) {
print_json($data, $indent, $comma);
}
else {
printf qq{"%s"%s\n}, $data, $comma;
}
}
# $indent and $comma (and $data) are restored here
#
printf "%s}%s\n", ' ' x $indent, $comma;
}
{
"ip" : {
"address" : "1.1.1.1",
"netmask" : "255.255.255.0"
},
"route" : {
"network" : "20.20.20.0",
"netmask" : "55.255.255.0",
"gateway" : "1.1.1.1"
}
}
答案 1 :(得分:1)
问题与XML解析没什么关系,但因为perl哈希值没有排序。所以当你写'&#39;一些JSON ......它可以是任何订单。
避免这种情况的方法是将排序函数应用于JSON。
您可以使用sort_by
明确排序:
#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;
use JSON::PP;
use Data::Dumper;
sub order_nodes {
my %rank_of = ( ip => 0, route => 1, address => 2, network => 3, netmask => 4, gateway => 5 );
print "$JSON::PP::a <=> $JSON::PP::b\n";
return $rank_of{$JSON::PP::a} <=> $rank_of{$JSON::PP::b};
}
my $twig = XML::Twig -> parse (\*DATA);
my $json = JSON::PP -> new;
$json ->sort_by ( \&order_nodes );
print $json -> encode( $twig -> simplify );
__DATA__
<Config>
<ip>
<address>1.1.1.1</address>
<netmask>255.255.255.0</netmask>
</ip>
<route>
<network>20.20.20.0</network>
<netmask>55.255.255.0</netmask>
<gateway>1.1.1.1</gateway>
</route>
</Config>
在某些情况下,设置canonical
会有所帮助,因为它会将排序设置为词法顺序。 (并且意味着您的JSON输出将始终如一地排序)。这并不适用于您的情况。
您可以通过XML::Twig
表达式或xpath
来twig_handlers
构建节点排序。我快速地试了一下,但是在弄清楚你是怎么告诉他的时候,我有点不知所措。如何根据获得address/netmask
然后network/netmask/gateway
计算排序。
作为一个简单的例子,您可以:
my $count = 0;
foreach my $node ( $twig -> get_xpath ( './*' ) ) {
$rank_of{$node->tag} = $count++ unless $rank_of{$node->tag};
}
print Dumper \%rank_of;
这将确保ip
和route
始终是正确的方法。但是它不会对子键进行排序。
这实际上变得有点复杂,因为你需要递归......然后决定如何处理碰撞事故&#39; (例如netmask
- address
之前,但与network
相比,它是如何排序的。
或者:
my $count = 0;
foreach my $node ( $twig->get_xpath('.//*') ) {
$rank_of{ $node->tag } = $count++ unless $rank_of{ $node->tag };
}
这将遍历所有节点,并将它们按顺序排列。它不起作用,因为netmask
出现在两个节中。
你得到:
{"ip":{"address":"1.1.1.1","netmask":"255.255.255.0"},"route":{"netmask":"55.255.255.0","network":"20.20.20.0","gateway":"1.1.1.1"}}
我无法找到折叠两个列表的巧妙方法。