gpt4 book ai didi

regex - 如何在正则表达式的多行中删除特殊字符?

转载 作者:行者123 更新时间:2023-12-03 13:45:35 25 4
gpt4 key购买 nike

我正在尝试解决一个问题,该问题想从文件中显示给定的文本,而忽略特殊字符,并将多行输入修改为仅使用Perl/Regex语言的单格式输出(没有其他语言,如XML, ETC。)。这是我的flight.txt文件中的给定文本:

<start> 
<flight number="12345">
<pilot> Holland, Tom</pilot>
<major>Aeronautics Engineer</major>
<company>Boeing</company>
<price>200</price>
<date>06-09-1969</date>
<details>Flight from DC to VA.</details>
</flight>
</start>

所需的输出是:
Holland, T. "Aeronautics Engineer" 200 06/09/1969 Flight from DC to VA.

如您所见,我需要在一行中输出;并且名字应该是名字的首字母缩写,而输出也必须是“”,而输出和日期的格式应该从 -更改为 /

到目前为止,这就是我的代码:
#!/bin/perl
use strict;
use warnings;
my $filename = "flights.txt"
open(my $input, '<:encoding(UTF-8)', $filename)
or die "Could not open file '$filename' $!";
while (my $row = <$input>){
my $text = <>;
$text =~ s/<[^>]*>//g;
print $text;
}
close $input

请建议我下一步做什么以及如何格式化给定文件的输出。我是Regex&Perl的新手,所以需要帮助。

最佳答案

正如您在对池上的答案的评论中指出的那样,这是您的作业问题:

Create the Perl script “code.pl” that print lines that contain an opening and closing XML heading tag from “flights.txt”. Valid tags are pilot, major, company, price, date, and details regardless of the case. Tags may also have any arbitrary content inside of them. You may assume that a '<' or a '>' character will not appear inside of the attribute's value portion


忘记由于ikegami已经解释的所有原因,您的输入是XML。整个过程都是一个精心设计的示例,可让您练习一些特定的正则表达式功能。我将经历一个解决此问题的过程,但稍后还会透露我认为老师期望的内容。
首先,您一次只需要考虑一行,因此您不必关心打开和关闭在单独的行上的节点,例如 <start></start><flight></flight>。您要查找以下行:
<node>...</node>
模式是在行的开头附近有一些您要匹配的字符串,并且该匹配项必须在行的后面显示。我认为您打算执行的任务是练习反向引用。编写好的练习非常困难,而且人们会回过头来熟悉诸如XML之类的东西。我的 Learning Perl Exercises对这个问题比较周到。
您的基本程序需要看起来像这样的第一次尝试。读取输入行,跳过与您的模式不匹配的行,然后输出其余行。每当您在此答案中看到 ...时,这只是我需要填写的内容,而不是Perl语法(忽略 yada operator,它不能出现在正则表达式中):
use strict;
use warnings;
while( <> ) {
next unless m/ ... /;
print;
}
我将主要忽略该程序结构,而将重点放在匹配运算符 m//上。当我逐步执行此操作时,请更新模式。
因此,诀窍在于模式。您知道必须匹配看起来像XML open标签的内容(再次,忽略它是XML,因为它不是输入的好例子)。它以 <开头,以 >结尾,中间有一些东西。此模式使用 /x标志使空格无关紧要。我可以展开模式,以便更轻松地查看它:
m/ < ... > /x;
那么尖括号里面有什么呢?在我假装的inputL不是XML的情况下,这些角度内的内容遵循以下规则,如果是XML,则可以在XML标准中阅读这些规则:
  • 区分大小写
  • 以字母或下划线开头
  • 可以包含字母,数字,连字符,下划线和句点
  • 在任何情况下都不能以xml开头

  • 让我们暂时忽略最后一个,因为我认为这不是您需要做的简单练习的一部分。规则实际上稍微复杂一些。
    区分大小写很容易。我们不会在match运算符上使用 /i标志,因此我们免费获得它。
    以字母或下划线开头。那很容易。因为我假装这不是XML,所以我将不支持当前XML允许的所有Unicode脚本。我将其限制为ASCII,并使用字符类表示在 >之后我将允许的所有字母:
    m/ < [a-zA-Z_] ... > /x;
    在那之后,我可以有字母和下划线,但现在也有连字符,数字和句点。顺便说一句,许多这样的事情在“标识符”( ID_Start)的开头都有一组字符,而对于其余的字符( ID_Continue)则有较宽的字符集。 Perl的变量名也有类似的规则。
    我使用第二个字符类作为延续。这里有一个小问题,因为您想要一个文字连字符,但这也构成了字符类中的一个范围。也就是说,它形成一个范围,除非它在末尾。字符类中的 .是文字 .:
    m/ < [a-zA-Z_] [a-zA-Z_0-9.-]+ > /x;
    通过这种模式,您可以获得比您想要的更多的东西。输出是具有开始标记的每一行。请注意,它与 <flight number="12345">不匹配,因为此模式不处理属性,这很好,因为我假装这不是XML:
    <start>
    <pilot> Holland, Tom</pilot>
    <major>Aeronautics Engineer</major>
    <company>Boeing</company>
    <price>200</price>
    <date>06-09-1969</date>
    <details>Flight from DC to VA.</details>
    结束标签与开始标签的名称相同。在我们的输入中,每行有一个开始标记和一个结束标记,并且由于我一次只看一行,因此我可以忽略XML解析器必须关心的许多事情。现在,我将模式分布在几行上,因为 /x允许我执行此操作,并且 \x也允许我添加注释,以便记住模式的每个部分的功能。 end标记中的 /也是匹配运算符分隔符,因此我将其作为 \/进行了转义:
    m/ 
    < [a-zA-Z_] [a-zA-Z_0-9.-]+ > # start tag
    ... # the interesting text
    < \/ ... > # end tag
    /x;
    我需要填写 ...部分。 “有趣的文字”部分很容易。我会匹配的。 .*贪婪地匹配零个或多个非换行符:
    m/ 
    < [a-zA-Z_] [a-zA-Z_0-9.-]+ > # start tag
    .* # the interesting text, greedily
    < \/ ... > # end tag
    /x;
    但是,我真的不希望 *贪婪。我不希望它与结束标签匹配,因此我可以将非贪婪修饰符 ?添加到 .*:
    m/ 
    < [a-zA-Z_] [a-zA-Z_0-9.-]+ > # start tag
    .*? # the interesting text, non-greedily
    < \/ ... > # end tag
    /x;
    现在,我需要填写结束标签的名称部分。它必须与起始名称相同。通过将起始名称括在 (...)中,我捕获了匹配的字符串部分。这进入捕获缓冲区 $1。然后,我可以在模式中使用“反向引用”(我想是问题的重点)重复使用该完全匹配。反向引用以 \开头,并使用您要使用的捕获缓冲区的编号。因此, \1使用 $1中匹配的确切文本;模式不相同,但实际文本匹配:
    m/ 
    < # start tag
    ([a-zA-Z_] [a-zA-Z_0-9.-]+) # $1
    >
    .*? # the interesting text, non-greedily
    < \/ \1 > # end tag
    /x;
    现在,输出不包含 <start>,因为它没有结束标签:
    <pilot> Holland, Tom</pilot>
    <major>Aeronautics Engineer</major>
    <company>Boeing</company>
    <price>200</price>
    <date>06-09-1969</date>
    <details>Flight from DC to VA.</details>
    如果您修改了数据以将 </date>更改为 </data>,那么该行将不匹配,因为开始和结束标记不同。
    但是,您真正想要的是中间的文本,因此您也需要捕获它。您可以添加另一个捕获缓冲区。作为第二组parens,这是缓冲区 $2,并且不会打扰 $1\1:
    m/ 
    < # start tag
    ([a-zA-Z_] [a-zA-Z_0-9.-]+) # $1
    >
    ( .*? ) # $2, the interesting text, non-greedily
    < \/ \1 > # end tag
    /x;
    但是现在您要打印有趣的测试,而不是整行,因此我将打印 $2捕获缓冲区,而不是整行。请记住,这些缓冲区仅在成功匹配后才有效,但是我跳过了不匹配的行,所以很好:
    use strict;
    use warnings;

    while( <DATA> ) {
    next unless m/
    < # start tag
    ([a-zA-Z_] [a-zA-Z_0-9.-]+) # $1
    >
    (.*?) # $2, the interesting text, non-greedily
    < \/ \1 > # end tag
    /x;

    print $2;
    }

    print "\n"; # end all the output!
    这使我接近。我在元素之间缺少一些空格(注意 Holland之前有一个前导空格):
     Holland, TomAeronautics EngineerBoeing20006-09-1969Flight from DC to VA.
    我可以在每个打印的末尾添加一个空格:
        print $2, ' ';
    现在您有了输出:
      Holland, Tom Aeronautics Engineer Boeing 200 06-09-1969 Flight from DC to VA.
    答案可能是
    我猜您将看到的答案要简单得多。如果您忽略关于名称的所有规则,而仅处理问题的确切输入,则可能可以逃脱此操作:
    m/ <(.*?)> (.*?) < \/ \1 > /x
    作为仅练习反向引用的练习,就可以了。但是,最终您将在处理像这样的真实XML时遇到问题。请注意, $1可以捕获所有 flight number="1234",因为这并不排除空格或其他不允许的字符。
    让我们更深入一点
    我展示的模式非常复杂,特别是如果您只是在学习东西。我可以预编译模式并将其保存在标量中,然后在match运算符中使用该标量:
    use strict;
    use warnings;

    my $pattern = qr/
    < # start tag
    ([a-zA-Z_] [a-zA-Z_0-9.-]+) # $1
    >
    ( .*? ) # the interesting text, non-greedily
    < \/ \1 > # end tag
    /x;

    while( <DATA> ) {
    next unless m/$pattern/;
    print $2, ' ';
    }
    这样, while循环的机制就与细节有所不同。模式的复杂性不会影响我理解循环的能力。
    现在,这样做之后,我将变得更加复杂。到目前为止,我使用了编号的捕获和反向引用,但是如果添加更多捕获,则可能会搞砸。如果在开始标记之前还有另一个捕获,则开始标记捕获不再是 $1,这意味着 \1现在引用了错误的内容。我可以用Perl从Python窃取的 (?<LABEL>...)功能代替数字,给他们自己的标签。对该标签的反向引用是 \k<LABEL>:
    my $pattern = qr/
    < # start tag
    (?<tag> # labeled capture
    [a-zA-Z_] [a-zA-Z_0-9.-]+
    )
    >
    ( .*? ) # the interesting text, non-greedily
    < \/ \k<tag> > # end tag
    /x;
    我什至可以标记“有趣的文字”部分:
    my $pattern = qr/
    < # start tag
    (?<tag>
    [a-zA-Z_] [a-zA-Z_0-9.-]+
    )
    >
    (?<text> .*? ) # the interesting text, non-greedily
    < \/ \k<tag> > # end tag
    /x;
    该程序的其余部分仍然有效,因为这些标签是编号捕获变量的别名。但是,我不想依赖于此(因此,标签)。哈希 %+在标记的捕获中具有值,并且标签是键。有趣的文本在 $+<text>中:
    while( <DATA> ) {
    next unless m/$pattern/;
    print $+{'text'}, ' ';
    }
    我忽略的规则
    现在,有一个我忽略的规则。标记名称在任何情况下都不能以 xml开头。这与XML功能有关,在这里我将忽略。我将更改输入以包括 xmlmeal节点:
    <start>
    <flight number="12345">
    <pilot> Holland, Tom</pilot>
    <xmlmeal> chicken</xmlmeal>
    </flight>
    </start>
    我匹配该 xmlmeal节点,因为我没有做任何事情来遵循规则。我可以添加否定的超前断言 (?!...)以排除该断言。作为断言( \b\A是其他断言),超前不消耗文本;它不会消耗文本。它只符合条件。我使用 (?!xml)来表示“无论我现在在哪里, xml都不能是下一个”:
    my $pattern = qr/
    < # start tag
    (?<tag>
    (?!xml)
    [a-zA-Z_] [a-zA-Z_0-9.-]+
    )
    >
    (?<text> .*? ) # the interesting text, non-greedily
    < \/ \k<tag> > # end tag
    /x;
    很好,它不会在输出中显示“chicken”。但是,如果输入的标签名称是 XMLmeal怎么办?我只排除了小写版本。我需要排除更多:
    <start>
    <flight number="12345">
    <pilot> Holland, Tom</pilot>
    <XMLmeal>chicken</XMLmeal>
    <xmldrink>diet coke</xmldrink>
    <Xmlsnack>almonds</Xmlsnack>
    </flight>
    </start>
    我可以做得更好。我不使用 /i标志来区分大小写,因为开始和结束标记需要完全匹配。但是,我可以使用 (?i)为模式的一部分打开不区分大小写的功能,并且所有过去的情况都将忽略大小写:
    my $pattern = qr/
    < # start tag
    (?<tag>
    (?i) # ignore case starting here
    (?!xml)
    [a-zA-Z_] [a-zA-Z_0-9.-]+
    )
    >
    (?<text> .*? ) # the interesting text, non-greedily
    < \/ \k<tag> > # end tag
    /x;
    但是,在分组括号内, (?i)仅在该组结束之前有效。我可以限制模式的哪一部分忽略大小写。没有捕获的 (?: ... )组(因此不会打扰 $1$2捕获):
    (?: (?i) (?!xml) )
    现在,我的模式不包括我添加的这三个标签:
    my $pattern = qr/
    < # start tag
    (?<tag>
    (?: (?i) (?!xml) ) # not XmL in any case
    [a-zA-Z_] [a-zA-Z_0-9.-]+
    )
    >
    (?<text> .*? ) # the interesting text, non-greedily
    < \/ \k<tag> > # end tag
    /x;
    一些魔咒
    到目前为止,我所介绍的内容都无法处理标记中的属性,无论如何您都希望忽略它们。您应该可以自己将它们添加到正则表达式中。但是,我将把齿轮转移到其他方法来处理类似XML的事情。
    这是一个Mojolicious程序,可以理解XML并可以提取内容。由于它是一个真正的文档对象模型(DOM)解析器,因此它不在乎行。
    #!perl

    use Mojo::DOM;

    my $not_xml = <<~'HERE';
    <start>
    <flight number="12345">
    <pilot> Holland, Tom</pilot>
    <major>Aeronautics Engineer</major>
    <company>Boeing</company>
    <price>200</price>
    <date>06-09-1969</date>
    <details>Flight from DC to VA.</details>
    </flight>
    </start>
    HERE

    Mojo::DOM->new( $not_xml )->xml(1)
    ->find( 'flight *' )
    ->map( 'text' )
    ->each( sub { print "$_ " } );

    print "\n";
    find使用CSS选择器来决定要处理的内容。选择器 flight *是Flight中的所有子节点(因此,任何子标签,无论其名称如何)。 maptext生成的树的每个部分上调用 find方法,并且 each输出每个结果。这很简单,因为有人已经完成了所有艰苦的工作。
    但是, Mojo::DOM并不适合每种情况。它想一次知道整棵树,而对于非常大的文档,这会增加内存负担。有“流”解析器可以处理此问题。
    wi
    您在原始问题中提出的问题与您在评论中发布的家庭作业不同。您要根据文本来自哪个标签来对其进行转换。这是另一种不同的问题,因为
    XML::Twig对于不同地处理不同的节点类型很有用。它的另一个优点是它不需要一次在内存中存储整个XML树。
    这是一个对飞行员和主要部分使用两种不同处理程序的示例。当Twig遇到这些节点时,它将调用您在 twig_handlers中引用的适当子例程。我不会在这里解释特定的Perl功能:
    use XML::Twig;

    my $twig = XML::Twig->new(
    twig_handlers => {
    pilot => \&pilot,
    major => \&major,
    },
    );

    sub pilot {
    my( $twig, $e ) = @_;
    my $text = $e->text;
    $text =~ s/,\s.\K.*/./;
    print $text, ' ';
    $twig->purge;
    }

    sub major {
    my( $twig, $e ) = @_;
    print '"' . $e->text . '"' . ' ';
    $twig->purge;
    }

    my $xml = <<~'HERE';
    <start>
    <flight number="12345">
    <pilot> Holland, Tom</pilot>
    <major>Aeronautics Engineer</major>
    <company>Boeing</company>
    <price>200</price>
    <date>06-09-1969</date>
    <details>Flight from DC to VA.</details>
    </flight>
    </start>
    HERE

    $twig->parse($xml);
    输出:
     Holland, T. "Aeronautics Engineer"
    现在,您将使用要处理的所有其他内容的子例程完成该操作。

    关于regex - 如何在正则表达式的多行中删除特殊字符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62270303/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com