Perl удаляет дубликаты тегов XML - PullRequest
0 голосов
/ 05 июля 2019

У меня есть следующий 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 ]

3 голосов
/ 05 июля 2019

Ниже приведен распространенный способ отфильтровать дубликаты:

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, поскольку вы не упомянули, какой анализатор вы использовали.)

2 голосов
/ 05 июля 2019

Использование 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 для этого.
2 голосов
/ 05 июля 2019

Любой, кто знает что-нибудь о XML, скажет вам не делать это с помощью обработки регулярных выражений, а с помощью правильного анализатора XML и инструментов XML. Вероятно, это можно сделать с помощью регулярных выражений (но не мной), если вы знаете, что формат файла всегда будет точно таким, как вы его показали, например, с символами новой строки, двойными кавычками и порядком атрибутов точно так же, как в вашем примере. Но если вы введете это в работу, то кто-то, генерирующий XML, через несколько лет спросит StackOverflow, как убедиться, что он может генерировать XML именно в этом формате, потому что принимающее приложение ломается, если атрибуты находятся в неправильном порядке или используйте одинарные кавычки, а не двойные. Итак, вы создаете проблемы на будущее. (Запомните закон Постеля, который в данном случае означает, что вы должны принимать любой правильно сформированный 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.

...