修复 xml 文件的多行没有 id 分隔

Fix multiple lines of an xml file without id to separate

我有一个外部生成的大型 xml 文件,其中包含一些无效字符,在我的例子中是一个反斜杠。我知道用什么替换这些字段,所以我可以对单个文件进行 gedit 并手动修复它。然而,有许多这样的文件,都存在同样的问题。我想写一个 bash 脚本来修复它们。

问题 有问题的部分如下所示。

<root>
 <array>
  <dimension> dim="1">gridpoints</dimension>
  <field> a </field>
  <field> b </field>
  <field> c </field>
  <field> [=11=][=11=][=11=] </field>
  <field> [=11=][=11=][=11=] </field>
  <field> [=11=][=11=][=11=] </field>
  <set> 
   All the data 
  </set>
 </array>
</root>

期望输出

<root>
 <array>
  <dimension> dim="1">gridpoints</dimension>
  <dimension> dim="2">morepoints</dimension>
  <dimension> dim="3">evenmorepoints</dimension>
  <field> a </field>
  <field> b </field>
  <field> c </field>
  <field> d </field>
  <field> e </field>
  <field> f </field>
  <set> 
   All the data 
  </set>
 </array>
</root>

到目前为止已修复 我已经找到了一种使用 perl 删除有问题的反斜杠的方法,但是我不知道如何像下面的代码那样单独编辑字段获得所需的解决方案,但每个字段都有条目“a”

#!/bin/bash
perl -CSDA -pe'
   s/[^\x9\xA\xD\x20-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]+//g;
' file.xml > temp.xml
xmlstarlet ed -u "/root/array/field" -v "a" temp.xml > file_fixed.xml

我也很乐意接受有关如何更有效地执行此操作的任何建议。谢谢。

编辑 应 zdim 的要求,我添加了一个更能代表我正在处理的完整文件的示例。

<root>
 <path1>
  <array>
   <dimension> dim="1">gridpoints</dimension>
   <field> a </field>
   <field> b </field>
   <field> c </field>
   <field> [=14=][=14=][=14=] </field>
   <field> [=14=][=14=][=14=] </field>
   <field> [=14=][=14=][=14=] </field>
   <set> 
    All the data 
   </set>
  </array>
 </path1>
 <path2>
  <array>
   <dimension> dim="1">gridpoints</dimension>
   <field> Behaves Correctly </field>
  </array>
 </path2>
</root>

应该注意的是,我从另一个程序接收这些文件作为输出,然后需要在将它们提供给下一个程序之前修复它们。我对 xml 几乎没有经验,这就是为什么我可能错过了一些明显的解决方案。

使用正确的 XML 解析器。

XML::LibXML,一种方式

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

use XML::LibXML;

my $filename = shift // die "Usage: [=10=] file.xml\n";  #/ fix syntax hilite

my $doc = XML::LibXML->load_xml(location => $filename);

# Remove unwanted nodes
foreach my $node ($doc->findnodes('//field')) { 
    #say $node->toString;   
    if ($node->toString =~ m{\00\00\00}) {
        say "Removing $node";
        $node->parentNode->removeChild($node);
    }   
}

# Add desired new nodes (right after the last <field> node)
my $last_field_node = ( $doc->findnodes('//field') )[-1];
my $field_node_name = $last_field_node->nodeName;
my $parent = $last_field_node->parentNode;

for ("E".."F") {
    my $new_elem = $doc->createElement( $field_node_name );
    $new_elem->appendText($_);
    $parent->insertAfter($new_elem, $last_field_node);
}

# Add other nodes (like the mentioned "dimension") the same way

print $doc->toString;

我使用基本的正则表达式来识别要删除的模式,如示例中所示。请根据您的实际输入调整代码。

这会在最后一个 <field> 节点之后添加新节点。但是如果我们需要在删除的节点之后添加,虽然可能还有更多的 <field> 节点,然后首先在需要删除的最后一个 <field> 节点之后添加,然后再删除它们。

或者,也许您只需要将 <field> 节点的内容替换为 '[=18=][=18=][=18=]'

my @replacements = "AA" .. "ZZ";  # li'l list of token replacements 

foreach my $node ($doc->findnodes('//field')) { 
    if ($node->toString =~ m{\00\00\00}) {
        say "Change $node -- remove child (text) nodes, add new";
        $node->removeChildNodes;
        $node->appendText(shift @replacements);
    }
}

一个元素的“值”实际上是一个text node,它有一个值。与其直接替换那个 (text-child-node's) 值,不如删除(所有)元素的(文本)-child-nodes,然后添加所需的新值。

此代码随后会处理 [=19=][=19=][=19=] 如果需要简单地替换这些代码,则从一些替换列表中提取。要同时添加 <dimension> 个节点,请使用 insertAfter 如上所述。

有更漂亮的打印模块,例如XML::LibXML::PrettyPrint


Mojo::DOM,一种方式

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

use Path::Tiny;  # convenience, for "slurp"-ing a file
use Mojo::DOM;

my $filename = shift // die "Usage: [=12=] file.xml\n";  #/ fix syntax hilite

my $dom = Mojo::DOM->new( path($filename)->slurp );
# my $dom = Mojo::DOM->new->xml(1)->parse(path($filename)->slurp);

# Remove unwanted, by filtering them first
$dom->find("field")
    -> grep( sub { $_->text =~ m{\00\00\00} } )
    -> each( sub { $_[0]->remove } );

# Or directly while iterating
# $dom->find("field")->each(
#     sub { $_[0]->remove if $_[0]->text =~ m{\00} } );

# Add new ones, after last 'field'
foreach my $content ("E".."F") {
    my $tag = $dom->new_tag('field', $content);
    $dom->find('field')->last->append($tag);
}

say $dom;

再次提醒,请根据实际文档结构进行调整。

一个例子。如果需要在要删除的 field 节点之后添加新的 field 节点(而不是在其他一些 field 节点之后),一种方法是先在这些节点之后添加,虽然我们仍然可以识别那些地方,然后才删除它们。

# Add new ones, after last 'field' that has [=13=][=13=][=13=] text in it
foreach my $content ("E".."F") {
    my $tag = $dom->new_tag('field', $content);
    $dom->find('field')->grep(sub { m{\00\00\00} })->last->append($tag);
}

# Only now remove those 'field' nodes with [=13=][=13=][=13=]
$dom->find("field")->each( 
    sub { $_[0]->remove if $_[0] =~ m{\00\00\00} } );

如果需要 (而不是 add-and-remove),使用此库也可以轻松替换节点的内容。