Perl删除重复的XML标签

时间:2019-07-05 02:08:02

标签: xml perl command-line duplicates

我有以下XML文件:

<d:entry id="a" d:title="a">
  <d:index d:value="a" d:title="a"/>
  <d:index d:value="b" d:title="b"/>
  <d:index d:value="a" d:title="a"/>
  <d:index d:value="c" d:title="c"/>
  <d:index d:value="b" d:title="b"/>
  <d:index d:value="a" d:title="a"/>
  <d:index d:value="b" d:title="b"/>
  <div>This is the content for entry.</div>
</d:entry>
<d:entry id="b" d:title="b">
  <d:index d:value="a" d:title="a"/>
  <d:index d:value="b" d:title="b"/>
  <div>This is the content for entry.</div>
</d:entry>

(添加空格以提高可读性。)

<d:index有一些重复项,我需要除去所有重复项,而只保留一个唯一的<d:index。所需的效果是这样的:

<d:entry id="a" d:title="a">
   <d:index d:value="a" d:title="a"/>
   <d:index d:value="b" d:title="b"/>
   <d:index d:value="c" d:title="c"/>
   <div>This is the content for entry.</div>
</d:entry>
<d:entry id="b" d:title="b">
  <d:index d:value="a" d:title="a"/>
  <d:index d:value="b" d:title="b"/>
  <div>This is the content for entry.</div>
</d:entry>

我可以为此目的在某些编辑器中进行正则表达式替换,但需要多次执行,我想知道Perl是否有某种方法可以一次运行。

3 个答案:

答案 0 :(得分:3)

以下是过滤重复项的常用方法:

my @filtered = grep { !$seen{$_}++ } @unfiltered;

这可以满足您的需求,如以下代码片段所示:

my %seen;
for my $index_node ($xpc->findnodes('d:index', $entry_node)) {
   my $value = $xpc->findvalue('@d:value', $index_node);
   my $title = $xpc->findvalue('@d:title', $index_node);
   if ($seen{$value}{$title}++) {
      $index_node->unbind();
   }
}

(我使用了首选的解析器XML :: LibXML,因为您没有提到您使用的是哪个解析器。)

答案 1 :(得分:2)

任何不了解XML的人都会告诉您不要使用正则表达式处理,而要使用适当的XML解析器和XML工具。如果您知道文件的格式将始终与显示的格式完全相同,则可以使用正则表达式(尽管不是我本人)来完成此操作,例如换行符,双引号和属性顺序完全与您的示例相同。但是,如果将其投入生产,那么生成XML的人将在一年后的StackOverflow上询问如何确保他们可以精确地以这种格式生成XML,因为如果属性顺序错误或使用单引号而不是双引号。因此,您正在为未来制造问题。 (请记住Postel的定律,在这种情况下,这意味着您应该接受任何等效于该XML的格式正确的XML。)

无论如何,在XSLT中执行此操作要比您提出的方法容易得多。假设您希望两个属性都匹配,以使元素计数为重复,则代码为:

<xsl:template match="d:entry">
<xsl:copy>
  <xsl:for-each-group select="d:index" 
                      group-by="concat(@d:value, '~', @d:title)">
     <xsl:copy-of select="current-group()[1]"/>
  </xsl:for-each-group>
  <xsl:copy-of select="div"/>
</xsl:copy>
</xsl:template>

顺便说一句,您说“添加了空格以提高可读性”。该空格,特别是包含换行符的空格,将对任何正则表达式解决方案产生重大影响,但对正确编写的XSLT则完全没有影响。

答案 2 :(得分:2)

使用Mojo::DOM

perl -MMojo::DOM -0777 -E'my $dom = Mojo::DOM->new->xml(1)->parse(<>);
  $dom->find(q{d\\:entry})->each(sub { my %seen;
    $_->find(q{d\\:index})->each(sub {
      $_->remove if $seen{$_->{"d:value"}}{$_->{"d:title"}}++ }) });
  print $dom->to_string' input.xml

结果:

<d:entry d:title="a" id="a">
  <d:index d:title="a" d:value="a" />
  <d:index d:title="b" d:value="b" />

  <d:index d:title="c" d:value="c" />



  <div>This is the content for entry.</div>
</d:entry>
<d:entry d:title="b" id="b">
  <d:index d:title="a" d:value="a" />
  <d:index d:title="b" d:value="b" />
  <div>This is the content for entry.</div>
</d:entry>
  • 如果实际内容没有这样的空格,则删除标签后不会将其保留。否则,更多的逻辑可以删除空白文本节点。
  • 我会为此使用ojo,但它没有XML模式解析的快捷方式。
  • 如果XML包含任何非ASCII字符,则需要在STDIN上对其进行解码,并根据其编码在STDOUT上对其进行编码;如果它是通常的UTF-8,则可以使用-CS开关执行此操作。