提问人:Kautuk Raj 提问时间:7/7/2023 更新时间:7/8/2023 访问量:75
从 Perl 中的 XML 文件中删除具有特定键的条目
Remove entries with specific keys from an XML file in Perl
问:
我有如下所示的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 解析器模块以及普通的旧正则表达式都很好。
答:
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
}
评论
git diff