从 Perl 中的 XML 文件中删除具有特定键的条目

Remove entries with specific keys from an XML file in Perl

提问人:Kautuk Raj 提问时间:7/7/2023 更新时间:7/8/2023 访问量:75

问:

我有如下所示的XML文件:

<?xml version="1.0" encoding="UTF-8"?>
<!-- some comment here -->
<rsccat version="1.0" locale="en_US" product="some_prouduct" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../product/resources/schema/msgcat.xsd">
  <message>

    <entry key="entry1" lol="false">
        <![CDATA[
            <actions>
                <action id="hmm" type="nothing">
                    <cmd>456</cmd>
                    <msg id="123"></msg>
                </action>
            </actions>
        ]]>
    </entry>

<entry key="entry2">message2 </entry>
<entry key="entry3">message3 </entry>

<entry key="entry4">
    <actions hello="yes">
    <action type="lol">
    <cmd>rolf</cmd>
    <txt>omg</txt>
    </action>
    </actions> </entry>



</message>
</rsccat>

我想在Perl中编写一个函数,该函数接受XML文件的路径和要删除的键列表,并完全删除与这些键关联的条目,而不会留下任何空格或空行。此外,我希望保留原始XML文件中现有的空行,例如,在带有键的条目之后的三个空行。entry4

我编写了一个函数,可以在不留下任何空行的情况下删除条目,但它也会删除XML文件中现有的空行。

use File::Slurp;  
sub findReplaceFile
{
    my ($filename, @keys) = @_;  

    my $filetext = read_file($filename);

    foreach my $key (@keys) 
    {
        chomp($key);  # remove newline characters
        my $regex = qr/<entry\s+key\s*=\s*"${key}".*?>.+?<\/entry>/s;
        $filetext =~ s/$regex//gs;  # replacing with empty string
        $filetext =~ s/\n\s*\n/\n/g;  # removing extra line
    }
}

请帮助我实现我的目标,我对 Perl 中的 XML 解析器模块以及普通的旧正则表达式都很好。

正则表达式 xml perl xml 解析 cpan

评论

1赞 Yitzhak Khabinsky 7/7/2023
(1) 最好使用 XSLT 来完成您的任务。(2) 您可以将密钥的参数传递给 XSLT。(3) 空格与 XML 文件无关。这就是为什么不清楚为什么需要保留现有的空白行。
0赞 Shawn 7/8/2023
如果空行很重要,它们不应该放在 CDATA 部分中吗?
0赞 Kautuk Raj 7/8/2023
空行很重要,因为在类似 ,删除它们将突出显示更多更改。@Shawngit diff

答:

0赞 e1st0rm 7/8/2023 #1

在不使用模块的情况下编写了一个示例。最有可能的是,在读取文件时,他们使用 chomp 函数,该函数会删除换行符。这不是最终的真理,而只是我的假设。这是我从未使用过的这个模块(File::Slurp)。文件 app.pl

#!/usr/bin/perl -w
use strict;

my $path = "data.xml";
findReplaceFile($path, "entry2", "entry4");


sub findReplaceFile {
    my ($filename, @keys) = @_;
    my $data = readData($filename);
    foreach my $key (@keys) {
        $data =~ s/<entry[^>]+key=(.?)$key\1[^>]*?>.*?<\/entry>\n?//mis;
    }
    writeData($filename, $data);
}

sub writeData {
    my $path = shift || "data.txt";
    my $data = shift || die "To write data to a file, you need to transfer this data";
    if (-e $path) {
        open my $fh, ">$path.dat" or die "Can't open file '$path.dat' for write: $!";
        print $fh $data;
        close $fh;
    }
}

sub readData {
    my $path = shift || "data.txt";
    my $data = "";
    if (-e $path and -T $path and -r $path) {
        open my $fh, "<$path" or die "Can't open file '$path' for read: $!";
        $data = join("", <$fh>);
        close $fh;
    } else {
        die "File '$path' dosn't exists or not a text file";
    }
    return $data;
}

此代码不会修改原始 XML。它会将结果保存在一个单独的文件中,将子字符串“.dat”添加到文件名中,在以下行中:

open my $fh, ">$path.dat" or die;

还应该注意的是,此代码将文件完全读取到内存中,如果您的文件增长到很大,您将需要重写算法以逐行读取文件,以及动态检查和替换。

以下代码行与上面的代码完全相同。在终端中运行此行,必须在此部分中指定密钥编号: (?:1|3) - 第一和第三 (?:1|3|2) - 第一、第三和第二 等。

perl -i.dat -ps0400e "s/<entry[^>]+key=(.?)entry(?:1|3)\1[^>]*?>.*?<\/entry>\n?//gmis" data.xml

只有现在原始文件才会以 .dat 扩展名保存,结果将保存到具有原始名称的文件中。

评论

1赞 ikegami 7/8/2023
为什么要努力编写自己的损坏解析器,而它对我们来说比现有的解析器更短、更可靠?!您的代码甚至不处理文档中使用的 CDATA 部分!
0赞 Kautuk Raj 7/8/2023
只是想指出,不需要从头开始使用读取函数。您建议的正则表达式解决了目的。
0赞 e1st0rm 7/8/2023
我在不到 5 分钟的时间内编写了这段代码,我认为您将需要更多时间来查找该模块并研究其文档。如果经常使用这样的模块,这可能是合理的。但是,如果您需要执行一次性任务,并且您确定 CDATA 不包含 <entry> 标记,那么此代码就可以了。下一行代码做同样的工作: perl -i.dat -ps0400e “s/<entry[^>]+key=(.?)entry(?:1|3)\1[^>]*?>.*?<\/entry>\n?//gmis“数据.xml
0赞 e1st0rm 7/11/2023
回复:你写出破损代码的速度真的重要吗?你为什么不把这五分钟花在正确的时间里呢?你从哪里得到代码被破坏的想法?我也不止一次地解析了XML,只是在这里它不是必需的。阅读作业条款。
-1赞 Kautuk Raj 7/8/2023 #2

回答我自己的问题,完成。

感谢@e1st0rm建议正则表达式。

use File::Slurp;  
sub findReplaceFile
{
    my ($filename, @keys) = @_;  

    my $filetext = read_file($filename);

    foreach my $key (@keys) 
    {
        $filetext =~ s/<entry[^>]+key=(.?)$key\1[^>]*?>.*?<\/entry>\n?//mis;
    }
    # Now, just write the data in variable filetext into the same or different file
}