perl 如何重命名XML元素名称,在运行时决定?

vmpqdwk3  于 7个月前  发布在  Perl
关注(0)|答案(3)|浏览(83)

我自己也试过一点,搜索了很多,但找不到一个如何在Perl中有效地做到这一点的解决方案(我猜这个解决方案有点类似于https://stackoverflow.com/a/22119220/6607497):
我有一些错误的XML输入文件(即它声称符合特定的XML内容模型,但元素名称的大小写不一致),如果有必要,我想修复。为此,我必须将每个元素名称与有效名称列表进行比较,如果错误的元素名称与有效的元素名称匹配,忽略名称的大小写,则应该将坏名称改变为相应的有效名称。
就像<Bad>...</Bad>(错误的大小写)被转换成<bad>...</bad>(正确的大小写)。当然,实际上它更复杂。而且,不好的标签使用混合大小写,而好的标签只使用小写并不总是正确的;它可以是任何组合。
我已经创建了一个所有有效元素名称的列表,但是我缺少(例如)如何使用XML::Twig为“任何节点”设置处理程序(希望在处理程序中使用set_tag来修复名称)。
创建一个标签的任意大小写排列的列表是可行的,但这似乎效率很低,因为所有这些可能的错误拼写中只有一小部分会在现实中发生。

Fancy Example

下面是一些有趣的例子:假设有效的元素名称是:

use constant GOOD_TAGS => qw(ABBA beard Elvis set ZZTop);

错误输入的示例如下所示:

<Set>
  <Beard type="none">
    <elvis />
  </Beard>
  <Beard type="long">
    <ZZtop />
  </Beard>
  <Beard type="mixed">
    <Abba />
  </Beard>
</Set>

则固定输出应为:

<set>
  <beard type="none">
    <Elvis />
  </beard>
  <beard type="long">
    <ZZTop />
  </beard>
  <beard type="mixed">
    <ABBA />
  </beard>
</set>

我不知道你可以使用编译后的正则表达式作为哈希键,但它似乎可以工作,所以你也可以假设这样的开始场景:

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

use constant GOOD_TAGS => qw(ABBA beard Elvis set ZZTop);
my %fixes;

foreach (GOOD_TAGS) {
    $fixes{qr/^${_}$/i} = $_;
}

my @matchers = keys %fixes;

因此,所有与@matchers中的项匹配的元素都应该重命名为相应的哈希值。

8cdiaqws

8cdiaqws1#

如何使用XML::Twig为“任何节点”设置处理程序
在创建XML::Twig对象时,将_all_用作为twig_handlers定义的处理程序中的键。
创建一个任意标记的任意大小写排列的列表是可行的,但似乎效率很低
确实如此。相反,我建议规范化好的标签,并检查您在XML中看到的规范化的标签是否与任何规范化的好标签匹配。在这里,可以通过转换为小写来完成规范化。类似于:

my %normalized_good_tags = map { lc($_) => $_ } GOOD_TAGS;
my $bad_tag = "BEaRD";
my $fixed_bad_tag = $normalized_good_tags{lc $bad_tag};

把所有这些放在一起,这给了我们:

use constant GOOD_TAGS => qw(ABBA beard Elvis set ZZTop);
my %normalized_good_tags = map { lc($_) => $_ } GOOD_TAGS;

my $twig = XML::Twig->new(
  keep_spaces => 1,
  twig_handlers => {
    _all_ => sub {
      my $corrected_tag = $normalized_good_tags{lc $_->tag};
      if (defined $corrected_tag) {
        $_->set_tag($corrected_tag);
      } # else, the tag doesn't need to be changed
    }
  });
$twig->parsefile($xml_file);
$twig->print(1);

你也可以简化

my $corrected_tag = $normalized_good_tags{lc $_->tag};
if (defined $corrected_tag) {
    $_->set_tag($corrected_tag);
}

$_->set_tag($normalized_good_tags{lc $_->tag} // $_->tag);

我对这两种方式都没有强烈的感觉,所以选择一个你最喜欢的。短一点的有点贵(因为它有时会调用$_->set_tag($_->tag)),但这可能不重要。

gorkyyrv

gorkyyrv2#

这里也使用XML::LibXML。它使用XML::LibXML::Node读取/设置节点名称。
遍历节点并根据好的列表检查它们的名称。在给定的示例中,可以将小写的node-name与小写(“规范化”)好的名称进行比较。由于
当然,实际上它更复杂。
实际比较标准符合实际。(Levenshtein距离?)

use warnings;
use strict;
use feature 'say';

use Const::Fast;
use XML::LibXML;

my $xml = <<'__XML__';
<Set>
  <Beard type="none">
    <elvis />
  </Beard>
  <Beard type="long">
    <ZZtop />
  </Beard>
  <Beard type="mixed">
    <Abba />
  </Beard>
</Set>
__XML__

const my %good      => map { $_ => 1 }     qw(ABBA beard Elvis set ZZTop);
const my %good_norm => map { lc $_ => $_ } keys %good;    

my $doc = XML::LibXML->new->parse_string($xml);

foreach my $node ($doc->findnodes('//*')) {
    my $name = $node->nodeName();

    if ( exists $good_norm{lc $name} and not exists $good{$name} ) {
        $node->setNodeName( $good_norm{lc $name} );
    }   
}

print $doc->toString();

考虑到所有节点都必须被访问(以某种方式)才能被检查,这在原则上大致是有效的。
如果好的列表中有所有的节点名,那么它只需要进行上面的第二个测试

if ( not exists $good{$name} ) {
        $node->setNodeName( $good_norm{lc $name} );
    }

实际上,您可能需要更多和/或不同的测试。

aiqt4smr

aiqt4smr3#

测试了XML::SAX解析器。看起来下面应该可以工作:

use v5.38;

package MySAXHandler;
use feature qw(say);
use strict;
use warnings;
use base qw(XML::SAX::Base);
use HTML::Entities ();

sub new {
    my $class = shift;
    #my $self = $class->SUPER::new();
    my %args = @_;
    my $self = bless \%args, $class;
    my $output_file = $self->{output_file};
    $self->{good_regex} = join "|", map {quotemeta} @{$self->{good_tags}};
    $self->{tags_seen} = {};
    open (my $fh, ">", $output_file) or die "Could not open file '$output_file': $!";
    $self->{fh} = $fh;
    return bless $self, $class;
}

sub characters {
    my ($self, $content) = @_;
    print {$self->{fh}} HTML::Entities::encode_entities($content->{Data});
}

sub end_document {
  my ($self, $doc) = @_;
  # process document end event
    $self->{fh}->close();
}

sub end_element {
    my ($self, $el) = @_;

    my $tag = $el->{Name};
    $tag = $self->fixup_tag($tag);
    print {$self->{fh}} "</" . $tag . ">"; # process element end event
}

sub find_good_tag {
    my ($self, $tag) = @_;

    for my $key (@{$self->{good_tags}}) {
        if ($tag =~ /$key/i) {
            $self->{tags_seen}{$tag} = $key;
            return $key;
        }
    }
    return undef;
}

sub fixup_tag {
    my ($self, $tag) = @_;
    my %tags_seen = %{$self->{tags_seen}};
    if ($tag =~ /($self->{good_regex})/i) {
        my $good_tag;
        if (exists $tags_seen{$tag}) {
            $good_tag = $tags_seen{$tag};
        }
        else {
            $good_tag = $self->find_good_tag($tag);
        }
        $good_tag = $tag if not defined $good_tag;
        return $good_tag;
    }
    return $tag;
}

sub get_attribute_string {
    my ($self, $attrs) = @_;

    my @attr_pairs = ();
    for my $key (keys %$attrs) {
        my $attr_name = $key =~ s/^\Q{}\E//r;  # TODO: This looks like a bug in XML::SAX ?
        my $attr_value = $attrs->{$key}->{Value};
        my $str = $attr_name . "=" . $attr_value;
        push @attr_pairs, $str;
    }
    my $str = "";
    $str = join " ", @attr_pairs;
    $str = " " . $str if $str ne "";
    return $str;
}

sub start_document {
  my ($self, $doc) = @_;
  # process document start event
}

sub start_element {
    my ($self, $el) = @_;
    # process element start event
    my $tag = $el->{Name};
    my $attrs = $el->{Attributes};
    $tag = $self->fixup_tag($tag);
    my $attr_str = $self->get_attribute_string($attrs);
    print {$self->{fh}} "<${tag}${attr_str}>";
}

package main;
use v5.38;
use XML::SAX;

my @GOOD_TAGS = qw(ABBA beard Elvis set ZZTop);
my $output_file = "output.xml";
my $handler = MySAXHandler->new(output_file => $output_file, good_tags => \@GOOD_TAGS);
my $p = XML::SAX::ParserFactory->parser(Handler => $handler);
$p->parse_file("input.xml");

相关问题