当正则表达式模式与字符串中的任何位置不匹配时该怎么办?

What to do when a regular expression pattern doesn't match anywhere in a string?

提问人:Salman 提问时间:11/20/2010 最后编辑:uzluisfSalman 更新时间:5/18/2023 访问量:68755

问:

我正在尝试使用这种模式匹配类型字段:<input>hidden

/<input type="hidden" name="([^"]*?)" value="([^"]*?)" />/

以下是一些示例表单数据:

<input type="hidden" name="SaveRequired" value="False" /><input type="hidden" name="__VIEWSTATE1" value="1H4sIAAtzrkX7QfL5VEGj6nGi+nP" /><input type="hidden" name="__VIEWSTATE2" value="0351118MK" /><input type="hidden" name="__VIEWSTATE3" value="ZVVV91yjY" /><input type="hidden" name="__VIEWSTATE0" value="3" /><input type="hidden" name="__VIEWSTATE" value="" /><input type="hidden" name="__VIEWSTATE" value="" />

但我不确定 、 和 属性是否总是以相同的顺序出现。如果属性排在最后,则匹配将失败,因为在我的模式中,它位于开头。typenamevaluetype

问:
如何更改我的模式,使其无论属性在标签中的位置如何都能匹配?
<input>

附言:顺便说一句,我正在使用基于Adobe AirRegEx桌面工具来测试正则表达式。

正则表达式 HTML 解析

评论

4赞 Salman 11/21/2010
如果您可以控制生成的 HTML,那么正则表达式是一个很好的解决方案,因为它是规则性的而不是常规的辩论,但是我的情况是我不知道 HTML 将来会如何变化,所以最好的办法是使用解析器而不是正则表达式,我已经在我的项目中使用了正则表达式,在我可以控制的部分中
2赞 Peter Mortensen 7/22/2020
Stack Overflow 经典是以“你不能用正则表达式解析 [X]HTML”开头的问题。
1赞 Andy Lester 5/11/2023
@PeterMortensen 不幸的是,这个答案对寻求知识的人没有帮助。这只对已经了解问题的人来说很有趣。

答:

113赞 Platinum Azure 11/20/2010 #1

与这里的所有答案相反,对于您要做的事情,正则表达式是一个完全有效的解决方案。这是因为您没有尝试匹配平衡标签 - 这对于正则表达式是不可能的!但是你只匹配一个标签中的内容,这是完全正常的。

不过,问题来了。你不能只用一个正则表达式来做到这一点......您需要进行一次匹配来捕获标签,然后对其进行进一步处理。请注意,这仅在所有属性值中都没有字符时才有效,因此它并不完美,但对于理智的输入来说应该足够了。<input>>

这里有一些Perl(伪)代码来告诉你我的意思:

my $html = readLargeInputFile();

my @input_tags = $html =~ m/
    (
        <input                      # Starts with "<input"
        (?=[^>]*?type="hidden")     # Use lookahead to make sure that type="hidden"
        [^>]+                       # Grab the rest of the tag...
        \/>                         # ...except for the />, which is grabbed here
    )/xgm;

# Now each member of @input_tags is something like <input type="hidden" name="SaveRequired" value="False" />

foreach my $input_tag (@input_tags)
{
  my $hash_ref = {};
  # Now extract each of the fields one at a time.

  ($hash_ref->{"name"}) = $input_tag =~ /name="([^"]*)"/;
  ($hash_ref->{"value"}) = $input_tag =~ /value="([^"]*)"/;

  # Put $hash_ref in a list or something, or otherwise process it
}

这里的基本原则是,不要试图用一个正则表达式做太多事情。正如你所注意到的,正则表达式强制执行一定数量的顺序。因此,您需要做的是首先匹配您尝试提取的内容的 CONTEXT,然后对所需的数据进行子匹配。

编辑:但是,我同意,一般来说,使用 HTML 解析器可能更容易、更好,您确实应该考虑重新设计您的代码或重新检查您的目标。:-)但是我不得不发布这个答案,以反驳下意识的反应,即解析任何HTML子集都是不可能的:当你考虑整个规范时,HTML和XML都是不规则的,但标签的规范是相当规则的,当然在PCRE的能力范围内。

评论

16赞 tchrist 11/21/2010
与这里的所有答案并不矛盾。:)
7赞 Platinum Azure 11/21/2010
@tchrist:当我发布我的答案时,你的答案不在这里。;-)
9赞 tchrist 11/21/2010
是的,好吧——出于某种原因,我打字的时间比你长。我想我的键盘一定需要润滑。:)
6赞 Ross Snyder 7/8/2011
这是无效的 HTML - 它应该是 value=“<你真的确定吗?>”如果他正在抓取的地方在逃避这样的事情方面做得很差,那么他将需要一个更复杂的解决方案——但如果他们做对了(如果他能控制它,他应该确保它是正确的),那么他就没事了。
14赞 Daniel Ribeiro 7/8/2011
关于该主题的最佳 SO 答案的强制性链接(可能是最佳 SO 答案期):stackoverflow.com/questions/1732348/......
726赞 tchrist 11/21/2010 #2

哦,是的,你可以使用正则表达式解析 HTML!

对于您正在尝试的任务,正则表达式是完全可以的!

的确,大多数人低估了使用正则表达式解析 HTML 的难度,因此做得很差。

但这并不是与计算理论相关的一些根本缺陷。这种愚蠢在这里被鹦鹉学舌,但你不相信他们。

因此,虽然它当然可以做到(这个帖子可以作为这个无可争辩的事实的存在证明),但这并不意味着它应该这样做。

你必须自己决定你是否能够胜任从正则表达式中编写一个专用的、专用的 HTML 解析器的任务。大多数人不是。

但我是。☻


基于正则表达式的通用 HTML 解析解决方案

首先,我将展示使用正则表达式解析任意 HTML 是多么容易。完整的程序在这篇文章的末尾,但解析器的核心是:

for (;;) {
  given ($html) {
    last                    when (pos || 0) >= length;
    printf "\@%d=",              (pos || 0);
    print  "doctype "   when / \G (?&doctype)  $RX_SUBS  /xgc;
    print  "cdata "     when / \G (?&cdata)    $RX_SUBS  /xgc;
    print  "xml "       when / \G (?&xml)      $RX_SUBS  /xgc;
    print  "xhook "     when / \G (?&xhook)    $RX_SUBS  /xgc;
    print  "script "    when / \G (?&script)   $RX_SUBS  /xgc;
    print  "style "     when / \G (?&style)    $RX_SUBS  /xgc;
    print  "comment "   when / \G (?&comment)  $RX_SUBS  /xgc;
    print  "tag "       when / \G (?&tag)      $RX_SUBS  /xgc;
    print  "untag "     when / \G (?&untag)    $RX_SUBS  /xgc;
    print  "nasty "     when / \G (?&nasty)    $RX_SUBS  /xgc;
    print  "text "      when / \G (?&nontag)   $RX_SUBS  /xgc;
    default {
      die "UNCLASSIFIED: " .
        substr($_, pos || 0, (length > 65) ? 65 : length);
    }
  }
}

看看这有多容易阅读?

正如所写的那样,它识别了每一段 HTML 并告诉它在哪里找到了该片段。您可以轻松地修改它,以对任何给定类型的作品或比这些更具体的类型做任何您想做的其他事情。

我没有失败的测试用例(左:):我已经成功地在超过 100,000 个 HTML 文件上运行了这段代码——每一个我都可以快速轻松地拿到手。除此之外,我还在专门为破坏朴素解析器而构建的文件上运行它。

这不是一个幼稚的解析器。

哦,我敢肯定它并不完美,但我还没有设法打破它。我认为即使有事情发生,由于程序的结构清晰,修复也很容易适应。即使是正则表达式的程序也应该有结构。

现在这已经不碍事了,让我来回答OP的问题。

使用正则表达式求解 OP 任务的演示

我在下面包含的小程序产生以下输出,因此您可以看到使用正则表达式解析 HTML 可以很好地执行您想要做的事情:html_input_rx

% html_input_rx Amazon.com-_Online_Shopping_for_Electronics,_Apparel,_Computers,_Books,_DVDs_\&_more.htm 
input tag #1 at character 9955:
       class => "searchSelect"
          id => "twotabsearchtextbox"
        name => "field-keywords"
        size => "50"
       style => "width:100%; background-color: #FFF;"
       title => "Search for"
        type => "text"
       value => ""

input tag #2 at character 10335:
         alt => "Go"
         src => "http://g-ecx.images-amazon.com/images/G/01/x-locale/common/transparent-pixel._V192234675_.gif"
        type => "image"

解析输入标签,查看没有恶意输入

这是生成上述输出的程序的源代码。

#!/usr/bin/env perl
#
# html_input_rx - pull out all <input> tags from (X)HTML src
#                  via simple regex processing
#
# Tom Christiansen <[email protected]>
# Sat Nov 20 10:17:31 MST 2010
#
################################################################

use 5.012;

use strict;
use autodie;
use warnings FATAL => "all";    
use subs qw{
    see_no_evil
    parse_input_tags
    input descape dequote
    load_patterns
};    
use open        ":std",
          IN => ":bytes",
         OUT => ":utf8";    
use Encode qw< encode decode >;

    ###########################################################

                        parse_input_tags 
                           see_no_evil 
                              input  

    ###########################################################

until eof(); sub parse_input_tags {
    my $_ = shift();
    our($Input_Tag_Rx, $Pull_Attr_Rx);
    my $count = 0;
    while (/$Input_Tag_Rx/pig) {
        my $input_tag = $+{TAG};
        my $place     = pos() - length ${^MATCH};
        printf "input tag #%d at character %d:\n", ++$count, $place;
        my %attr = ();
        while ($input_tag =~ /$Pull_Attr_Rx/g) {
            my ($name, $value) = @+{ qw< NAME VALUE > };
            $value = dequote($value);
            if (exists $attr{$name}) {
                printf "Discarding dup attr value '%s' on %s attr\n",
                    $attr{$name} // "<undef>", $name;
            } 
            $attr{$name} = $value;
        } 
        for my $name (sort keys %attr) {
            printf "  %10s => ", $name;
            my $value = descape $attr{$name};
            my  @Q; given ($value) {
                @Q = qw[  " "  ]  when !/'/ && !/"/;
                @Q = qw[  " "  ]  when  /'/ && !/"/;
                @Q = qw[  ' '  ]  when !/'/ &&  /"/;
                @Q = qw[ q( )  ]  when  /'/ &&  /"/;
                default { die "NOTREACHED" }
            } 
            say $Q[0], $value, $Q[1];
        } 
        print "\n";
    } 

}

sub dequote {
    my $_ = $_[0];
    s{
        (?<quote>   ["']      )
        (?<BODY>    
          (?s: (?! \k<quote> ) . ) * 
        )
        \k<quote> 
    }{$+{BODY}}six;
    return $_;
} 

sub descape {
    my $string = $_[0];
    for my $_ ($string) {
        s{
            (?<! % )
            % ( \p{Hex_Digit} {2} )
        }{
            chr hex $1;
        }gsex;
        s{
            & \043 
            ( [0-9]+ )
            (?: ; 
              | (?= [^0-9] )
            )
        }{
            chr     $1;
        }gsex;
        s{
            & \043 x
            ( \p{ASCII_HexDigit} + )
            (?: ; 
              | (?= \P{ASCII_HexDigit} )
            )
        }{
            chr hex $1;
        }gsex;

    }
    return $string;
} 

sub input { 
    our ($RX_SUBS, $Meta_Tag_Rx);
    my $_ = do { local $/; <> };  
    my $encoding = "iso-8859-1";  # web default; wish we had the HTTP headers :(
    while (/$Meta_Tag_Rx/gi) {
        my $meta = $+{META};
        next unless $meta =~ m{             $RX_SUBS
            (?= http-equiv ) 
            (?&name) 
            (?&equals) 
            (?= (?&quote)? content-type )
            (?&value)    
        }six;
        next unless $meta =~ m{             $RX_SUBS
            (?= content ) (?&name) 
                          (?&equals) 
            (?<CONTENT>   (?&value)    )
        }six;
        next unless $+{CONTENT} =~ m{       $RX_SUBS
            (?= charset ) (?&name) 
                          (?&equals) 
            (?<CHARSET>   (?&value)    )
        }six;
        if (lc $encoding ne lc $+{CHARSET}) {
            say "[RESETTING ENCODING $encoding => $+{CHARSET}]";
            $encoding = $+{CHARSET};
        }
    } 
    return decode($encoding, $_);
}

sub see_no_evil {
    my $_ = shift();

    s{ <!    DOCTYPE  .*?         > }{}sx; 
    s{ <! \[ CDATA \[ .*?    \]\] > }{}gsx; 

    s{ <script> .*?  </script> }{}gsix; 
    s{ <!--     .*?        --> }{}gsx;

    return $_;
}

sub load_patterns { 

    our $RX_SUBS = qr{ (?(DEFINE)
        (?<nv_pair>         (?&name) (?&equals) (?&value)         ) 
        (?<name>            \b (?=  \pL ) [\w\-] + (?<= \pL ) \b  )
        (?<equals>          (?&might_white)  = (?&might_white)    )
        (?<value>           (?&quoted_value) | (?&unquoted_value) )
        (?<unwhite_chunk>   (?: (?! > ) \S ) +                    )
        (?<unquoted_value>  [\w\-] *                              )
        (?<might_white>     \s *                                  )
        (?<quoted_value>
            (?<quote>   ["']      )
            (?: (?! \k<quote> ) . ) *
            \k<quote> 
        )
        (?<start_tag>  < (?&might_white) )
        (?<end_tag>          
            (?&might_white)
            (?: (?&html_end_tag) 
              | (?&xhtml_end_tag) 
             )
        )
        (?<html_end_tag>       >  )
        (?<xhtml_end_tag>    / >  )
    ) }six; 

    our $Meta_Tag_Rx = qr{                          $RX_SUBS 
        (?<META> 
            (?&start_tag) meta \b
            (?:
                (?&might_white) (?&nv_pair) 
            ) +
            (?&end_tag)
        )
    }six;

    our $Pull_Attr_Rx = qr{                         $RX_SUBS
        (?<NAME>  (?&name)      )
                  (?&equals) 
        (?<VALUE> (?&value)     )
    }six;

    our $Input_Tag_Rx = qr{                         $RX_SUBS 

        (?<TAG> (?&input_tag) )

        (?(DEFINE)

            (?<input_tag>
                (?&start_tag)
                input
                (?&might_white) 
                (?&attributes) 
                (?&might_white) 
                (?&end_tag)
            )

            (?<attributes>
                (?: 
                    (?&might_white) 
                    (?&one_attribute) 
                ) *
            )

            (?<one_attribute>
                \b
                (?&legal_attribute)
                (?&might_white) = (?&might_white) 
                (?:
                    (?&quoted_value)
                  | (?&unquoted_value)
                )
            )

            (?<legal_attribute> 
                (?: (?&optional_attribute)
                  | (?&standard_attribute)
                  | (?&event_attribute)
            # for LEGAL parse only, comment out next line 
                  | (?&illegal_attribute)
                )
            )

            (?<illegal_attribute>  (?&name) )

            (?<required_attribute> (?#no required attributes) )

            (?<optional_attribute>
                (?&permitted_attribute)
              | (?&deprecated_attribute)
            )

            # NB: The white space in string literals 
            #     below DOES NOT COUNT!   It's just 
            #     there for legibility.

            (?<permitted_attribute>
                  accept
                | alt
                | bottom
                | check box
                | checked
                | disabled
                | file
                | hidden
                | image
                | max length
                | middle
                | name
                | password
                | radio
                | read only
                | reset
                | right
                | size
                | src
                | submit
                | text
                | top
                | type
                | value
            )

            (?<deprecated_attribute>
                  align
            )

            (?<standard_attribute>
                  access key
                | class
                | dir
                | ltr
                | id
                | lang
                | style
                | tab index
                | title
                | xml:lang
            )

            (?<event_attribute>
                  on blur
                | on change
                | on click
                | on dbl   click
                | on focus
                | on mouse down
                | on mouse move
                | on mouse out
                | on mouse over
                | on mouse up
                | on key   down
                | on key   press
                | on key   up
                | on select
            )
        )
    }six;

}

UNITCHECK {
    load_patterns();
} 

END {
    close(STDOUT) 
        || die "can't close stdout: $!";
} 

给你!没什么!:)

只有您可以判断您的正则表达式技能是否能够胜任任何特定的解析任务。每个人的技能水平都不同,每个新任务都不同。对于具有明确定义的输入集的工作,正则表达式显然是正确的选择,因为当您要处理受限制的 HTML 子集时,将一些正则表达式放在一起是微不足道的。即使是正则表达式初学者也应该使用正则表达式处理这些工作。其他任何事情都是矫枉过正。

然而,一旦 HTML 开始变得不那么固定,一旦它开始以你无法预测但完全合法的方式分支,一旦你必须匹配更多不同类型的事物或更复杂的依赖项,你最终会达到一个点,你必须更加努力地工作才能实现使用正则表达式的解决方案,而不是使用解析类。盈亏平衡点的下降点再次取决于您自己对正则表达式的舒适度。

那我该怎么办?

我不会告诉你你必须做什么或你不能做什么。我认为这是错误的。我只想向你展示各种可能性,睁开你的眼睛。你可以选择你想做什么以及你想怎么做。世上没有绝对的,没有人比你自己更了解自己的处境。如果某件事看起来工作量太大,好吧,也许是。编程应该很有趣,你知道的。如果不是,你可能做错了。

人们可以用任何有效的方式查看我的程序。其中之一是您确实可以使用正则表达式解析 HTML。但另一个问题是,它比几乎任何人想象的都要困难得多。这很容易得出这样的结论,即我的程序证明了你不应该做什么,因为它真的太难了。html_input_rx

我不会不同意这一点。当然,如果经过一些研究,我在程序中所做的一切对你来说都没有意义,那么你就不应该尝试使用正则表达式来完成这种任务。对于特定的 HTML,正则表达式很棒,但对于通用 HTML,它们无异于疯狂。我一直使用解析类,特别是如果它不是我自己生成的 HTML。

正则表达式最适合的 HTML 解析问题,悲观的正则表达式适合大的 HTML 解析问题

即使我的程序被看作是说明为什么你不应该使用正则表达式来解析一般的HTML——这没关系,因为我有点想让它成为这样☺——它仍然应该让人大开眼界,让更多的人打破编写不可读、非结构化和不可维护的模式的非常普遍和令人讨厌的习惯。

图案不一定是丑陋的,也不一定是坚硬的。如果你创造了丑陋的图案,那是对你的反思,而不是它们。

非常精致的正则表达式语言

我被要求指出,我对你的问题提出的解决方案是用Perl编写的。你感到惊讶吗?你没注意到吗?这个启示是重磅炸弹吗?

诚然,并非所有其他工具和编程语言都像 Perl 那样方便、富有表现力和功能强大。那里有一个很大的范围,有些比其他的更合适。一般来说,将正则表达式作为核心语言的一部分而不是库的语言更容易使用。我没有对正则表达式做任何事情,比如说,PCRE,尽管如果你使用C,你会以不同的方式构建程序。

最终,其他语言将赶上Perl在正则表达式方面所处的位置。我之所以这样说,是因为当 Perl 开始的时候,没有其他人拥有像 Perl 的正则表达式这样的东西。随便说吧,但这就是 Perl 显然获胜的地方:每个人都复制了 Perl 的正则表达式,尽管它们处于不同的发展阶段。Perl 几乎(不是全部,但几乎)开创了你在当今现代模式中所依赖的一切,无论你使用什么工具或语言。所以最终其他人迎头赶上。

但是他们只会赶上Perl在过去某个时候所处的位置,就像现在一样。一切都在进步。在正则表达式中,如果不出意外的话,Perl 领先的地方,其他人就会跟随。一旦其他人最终赶上了Perl现在的位置,Perl将在哪里?我不知道,但我知道我们也会搬家。也许我们会更接近 Perl₆ 制作模式的风格

如果你喜欢这种东西,但又想在 Perl₅ 中使用它,你可能会对 Damian Conway 精彩的 Regexp::Grammars 模块感兴趣。这真是太棒了,让我在程序中所做的看起来和我所做的一样原始,使人们在没有空格或字母标识符的情况下塞在一起的模式。一探究竟!


简单的 HTML Chunker

这是我在这篇文章开始时展示的核心部分的解析器的完整源代码。

并不是建议你应该在经过严格测试的解析类上使用它。但是我厌倦了人们假装没有人可以用正则表达式解析HTML,因为他们不能。你显然可以,这个程序就是这一断言的证明。

当然,这并不容易,但这是可能的!

尝试这样做是浪费时间,因为存在良好的解析类,您应该将其用于此任务。对于试图解析任意 HTML 的人来说,正确的答案并不是说这是不可能的。这是一个轻率和虚伪的答案。正确而诚实的答案是,他们不应该尝试,因为从头开始弄清楚太麻烦了;他们不应该为了重新制造一个运行良好的轮子而折断自己的背部。

另一方面,属于可预测子集的 HTML 非常容易使用正则表达式解析。难怪人们试图使用它们,因为对于小问题,也许是玩具问题,没有什么比这更容易的了。这就是为什么区分这两个任务(特定任务与通用任务)如此重要的原因,因为它们不一定需要相同的方法。

我希望将来能在这里看到更公平和诚实地处理有关 HTML 和正则表达式的问题。

这是我的 HTML 词法分析器。它不会尝试进行验证解析;它只是标识词法元素。您可能认为它更像是一个 HTML 块,而不是一个 HTML 解析器。它对损坏的 HTML 不是很宽容,尽管它在这个方向上做了一些非常小的允许。

即使你自己从来没有解析过完整的HTML(你为什么要解析呢?这是一个已解决的问题!),这个程序有很多很酷的正则表达式,我相信很多人可以从中学到很多东西。享受!

#!/usr/bin/env perl
#
# chunk_HTML - a regex-based HTML chunker
#
# Tom Christiansen <[email protected]
#   Sun Nov 21 19:16:02 MST 2010
########################################

use 5.012;

use strict;
use autodie;
use warnings qw< FATAL all >;
use open     qw< IN :bytes OUT :utf8 :std >;

MAIN: {
  $| = 1;
  lex_html(my $page = slurpy());
  exit();
}

########################################################################
sub lex_html {
    our $RX_SUBS;                                        ###############
    my  $html = shift();                                 # Am I...     #
    for (;;) {                                           # forgiven? :)#
        given ($html) {                                  ###############
            last                when (pos || 0) >= length;
            printf "\@%d=",          (pos || 0);
            print  "doctype "   when / \G (?&doctype)  $RX_SUBS  /xgc;
            print  "cdata "     when / \G (?&cdata)    $RX_SUBS  /xgc;
            print  "xml "       when / \G (?&xml)      $RX_SUBS  /xgc;
            print  "xhook "     when / \G (?&xhook)    $RX_SUBS  /xgc;
            print  "script "    when / \G (?&script)   $RX_SUBS  /xgc;
            print  "style "     when / \G (?&style)    $RX_SUBS  /xgc;
            print  "comment "   when / \G (?&comment)  $RX_SUBS  /xgc;
            print  "tag "       when / \G (?&tag)      $RX_SUBS  /xgc;
            print  "untag "     when / \G (?&untag)    $RX_SUBS  /xgc;
            print  "nasty "     when / \G (?&nasty)    $RX_SUBS  /xgc;
            print  "text "      when / \G (?&nontag)   $RX_SUBS  /xgc;
            default {
                die "UNCLASSIFIED: " .
                  substr($_, pos || 0, (length > 65) ? 65 : length);
            }
        }
    }
    say ".";
}
#####################
# Return correctly decoded contents of next complete
# file slurped in from the <ARGV> stream.
#
sub slurpy {
    our ($RX_SUBS, $Meta_Tag_Rx);
    my $_ = do { local $/; <ARGV> };   # read all input

    return unless length;

    use Encode   qw< decode >;

    my $bom = "";
    given ($_) {
        $bom = "UTF-32LE" when / ^ \xFf \xFe \0   \0   /x;  # LE
        $bom = "UTF-32BE" when / ^ \0   \0   \xFe \xFf /x;  #   BE
        $bom = "UTF-16LE" when / ^ \xFf \xFe           /x;  # le
        $bom = "UTF-16BE" when / ^ \xFe \xFf           /x;  #   be
        $bom = "UTF-8"    when / ^ \xEF \xBB \xBF      /x;  # st00pid
    }
    if ($bom) {
        say "[BOM $bom]";
        s/^...// if $bom eq "UTF-8";                        # st00pid

        # Must use UTF-(16|32) w/o -[BL]E to strip BOM.
        $bom =~ s/-[LB]E//;

        return decode($bom, $_);

        # if BOM found, don't fall through to look
        #  for embedded encoding spec
    }

    # Latin1 is web default if not otherwise specified.
    # No way to do this correctly if it was overridden
    # in the HTTP header, since we assume stream contains
    # HTML only, not also the HTTP header.
    my $encoding = "iso-8859-1";
    while (/ (?&xml) $RX_SUBS /pgx) {
        my $xml = ${^MATCH};
        next unless $xml =~ m{              $RX_SUBS
            (?= encoding )  (?&name)
                            (?&equals)
                            (?&quote) ?
            (?<ENCODING>    (?&value)       )
        }sx;
        if (lc $encoding ne lc $+{ENCODING}) {
            say "[XML ENCODING $encoding => $+{ENCODING}]";
            $encoding = $+{ENCODING};
        }
    }

    while (/$Meta_Tag_Rx/gi) {
        my $meta = $+{META};

        next unless $meta =~ m{             $RX_SUBS
            (?= http-equiv )    (?&name)
                                (?&equals)
            (?= (?&quote)? content-type )
                                (?&value)
        }six;

        next unless $meta =~ m{             $RX_SUBS
            (?= content )       (?&name)
                                (?&equals)
            (?<CONTENT>         (?&value)    )
        }six;

        next unless $+{CONTENT} =~ m{       $RX_SUBS
            (?= charset )       (?&name)
                                (?&equals)
            (?<CHARSET>         (?&value)    )
        }six;

        if (lc $encoding ne lc $+{CHARSET}) {
            say "[HTTP-EQUIV ENCODING $encoding => $+{CHARSET}]";
            $encoding = $+{CHARSET};
        }
    }

    return decode($encoding, $_);
}
########################################################################
# Make sure to this function is called
# as soon as source unit has been compiled.
UNITCHECK { load_rxsubs() }

# useful regex subroutines for HTML parsing
sub load_rxsubs {

    our $RX_SUBS = qr{
      (?(DEFINE)

        (?<WS> \s *  )

        (?<any_nv_pair>     (?&name) (?&equals) (?&value)         )
        (?<name>            \b (?=  \pL ) [\w:\-] +  \b           )
        (?<equals>          (?&WS)  = (?&WS)    )
        (?<value>           (?&quoted_value) | (?&unquoted_value) )
        (?<unwhite_chunk>   (?: (?! > ) \S ) +                    )

        (?<unquoted_value>  [\w:\-] *                             )

        (?<any_quote>  ["']      )

        (?<quoted_value>
            (?<quote>   (?&any_quote)  )
            (?: (?! \k<quote> ) . ) *
            \k<quote>
        )

        (?<start_tag>       < (?&WS)      )
        (?<html_end_tag>      >           )
        (?<xhtml_end_tag>   / >           )
        (?<end_tag>
            (?&WS)
            (?: (?&html_end_tag)
              | (?&xhtml_end_tag) )
         )

        (?<tag>
            (?&start_tag)
            (?&name)
            (?:
                (?&WS)
                (?&any_nv_pair)
            ) *
            (?&end_tag)
        )

        (?<untag> </ (?&name) > )

        # starts like a tag, but has screwed up quotes inside it
        (?<nasty>
            (?&start_tag)
            (?&name)
            .*?
            (?&end_tag)
        )

        (?<nontag>    [^<] +            )

        (?<string> (?&quoted_value)     )
        (?<word>   (?&name)             )

        (?<doctype>
            <!DOCTYPE
                # please don't feed me nonHTML
                ### (?&WS) HTML
            [^>]* >
        )

        (?<cdata>   <!\[CDATA\[     .*?     \]\]    > )
        (?<script>  (?= <script ) (?&tag)   .*?     </script> )
        (?<style>   (?= <style  ) (?&tag)   .*?     </style> )
        (?<comment> <!--            .*?           --> )

        (?<xml>
            < \? xml
            (?:
                (?&WS)
                (?&any_nv_pair)
            ) *
            (?&WS)
            \? >
        )

        (?<xhook> < \? .*? \? > )

      )

    }six;

    our $Meta_Tag_Rx = qr{                          $RX_SUBS
        (?<META>
            (?&start_tag) meta \b
            (?:
                (?&WS) (?&any_nv_pair)
            ) +
            (?&end_tag)
        )
    }six;

}

# nobody *ever* remembers to do this!
END { close STDOUT }

评论

24赞 Salman 11/21/2010
您的评论中的两个亮点“我一直在使用解析类,特别是如果它是我自己没有生成的 HTML”和“模式不一定是丑陋的,也不一定是困难的。如果你创造了丑陋的图案,那是对你的反思,而不是它们。 我完全同意你所说的话,所以我正在重新评估这个问题。非常感谢您提供如此详细的答案
175赞 Bill Ruppert 2/17/2011
对于那些不知道的人,我想我应该提到 Tom 是“Programming Perl”(又名 Camel 书)的合著者,也是 Perl 的顶级权威之一。如果您怀疑这是真正的汤姆·克里斯蒂安森(Tom Christiansen),请回去阅读这篇文章。
23赞 Steve Steiner 7/8/2011
总而言之:正则表达式的名称错误。我认为这是一种耻辱,但它不会改变。兼容的“正则表达式”引擎不允许拒绝非常规语言。因此,仅使用Finte状态机无法正确实现它们。围绕计算类的强大概念并不适用。使用正则表达式并不能确保 O(n) 执行时间。正则表达式的优点是简洁的语法和字符识别的隐含域。对我来说,这是一辆缓慢移动的火车残骸,无法移开视线,但可怕的后果正在展开。
29赞 Qtax 12/27/2011
@tchrist,这永远无法回答 OP 的原始问题。解析在这里是正确的术语吗?正则表达式正在进行标记化/词法分析,但最终解析是用Perl代码完成的,而不是正则表达式本身。
71赞 Mike Clark 3/3/2012
@tchrist 非常令人印象深刻。你显然是一个技术娴熟、才华横溢的Perl程序员,对现代正则表达式非常了解。不过,我要指出的是,你所写的并不是一个真正的正则表达式(现代的、正则的或其他的),而是一个大量使用正则表达式的Perl程序。你的帖子真的支持正则表达式可以正确解析 HTML 的说法吗?或者它更像是Perl可以正确解析HTML的证据?无论哪种方式,干得好!
131赞 meder omuraliev 11/21/2010 #3
  1. 你可以像tchrist那样写小说
  2. 您可以使用 DOM 库,加载 HTML 并使用 xpath,然后只使用 .或者,如果您不想使用 xpath,只需获取所有输入并用 过滤隐藏的输入。//input[@type="hidden"]getAttribute

我更喜欢#2。

<?php

$d = new DOMDocument();
$d->loadHTML(
    '
    <p>fsdjl</p>
    <form><div>fdsjl</div></form>
    <input type="hidden" name="blah" value="hide yo kids">
    <input type="text" name="blah" value="hide yo kids">
    <input type="hidden" name="blah" value="hide yo wife">
');
$x = new DOMXpath($d);
$inputs = $x->evaluate('//input[@type="hidden"]');

foreach ( $inputs as $input ) {
    echo $input->getAttribute('value'), '<br>';
}

结果:

hide yo kids<br>hide yo wife<br>

评论

78赞 tchrist 11/21/2010
实际上,这就是我的观点。我想展示它有多难。
19赞 tchrist 11/21/2010
那里的东西非常好。我真的希望人们能证明使用解析类是多么容易,所以谢谢!我只是想要一个工作示例,说明使用正则表达式从头开始执行操作所必须经历的极端麻烦。我当然希望大多数人得出结论,在通用 HTML 上使用预制件解析器,而不是滚动自己的解析器。不过,正则表达式对于他们自己制作的简单 HTML 仍然很棒,因为它摆脱了 99.98% 的复杂性。
5赞 the_yellow_logo 8/31/2014
在阅读了这两种非常有趣的方法之后,将一种方法的速度/内存使用率/CPU 与另一种方法(即基于正则表达式的 VS 解析类)进行比较会很好。
1赞 Dennis98 9/20/2016
@Avt是的,并不是说如果正则表达式碰巧更快,你应该去写一本'小说',但事实上,知道它真的很有趣。:)但我的猜测已经是,解析器占用的资源也更少。
0赞 Thorbjørn Ravn Andersen 7/22/2017
这实际上就是 XPath 首先被发明的原因!
22赞 David 7/10/2011 #4

本着 Tom Christiansen 的词法分析器解决方案的精神,这里是 Robert Cameron 在 1998 年发表的一篇似乎被遗忘的文章 REX: XML Shallow Parsing with Regular Expressions(《REX:使用正则表达式的 XML 浅层解析》)的链接。

http://www.cs.sfu.ca/~cameron/REX.html

抽象

XML 的语法非常简单,可以使用单个正则表达式将 XML 文档解析为其标记和文本项的列表。XML文档的这种浅层解析对于构建各种轻量级XML处理工具非常有用。然而,复杂的正则表达式可能难以构造,甚至更难阅读。本文使用一种正则表达式的识字编程形式,记录了一组 XML 浅层解析表达式,这些表达式可用于简单、正确、高效、健壮和独立于语言的 XML 浅层解析的基础。还给出了 Perl、JavaScript 和 Lex/Flex 中每个少于 50 行的完整浅层解析器实现。

如果你喜欢阅读有关正则表达式的阅读,那么 Cameron 的论文很吸引人。他的文笔简洁、透彻、非常详细。他不仅向您展示了如何构造 REX 正则表达式,还向您展示了一种从较小的部分构建任何复杂正则表达式的方法。

10 年来,我一直在断断续续地使用 REX 正则表达式来解决最初发帖人提出的问题(我如何匹配这个特定的标签,而不是其他一些非常相似的标签?我发现他开发的正则表达式是完全可靠的。

当您专注于文档的词汇细节时,REX 特别有用 - 例如,将一种文本文档(例如,纯文本、XML、SGML、HTML)转换为另一种文本文档时,文档可能无效、格式不正确,甚至无法解析大多数转换。它允许您将标记岛定位到文档中的任何位置,而不会干扰文档的其余部分。

3赞 Shamshirsaz.Navid 4/1/2013 #5

你可以试试这个:

<[A-Za-z ="/_0-9+]*>

为了获得更接近的结果,您可以尝试以下方法:

<[ ]*input[ ]+type="hidden"[ ]*name=[A-Za-z ="_0-9+]*[ ]*[/]*>

您可以在此处测试您的正则表达式模式 http://regexpal.com/

这些 pattens 对此有好处:

<input type="hidden" name="SaveRequired" value="False" /><input type="hidden" name="__VIEWSTATE1" value="1H4sIAAtzrkX7QfL5VEGj6nGi+nP" /><input type="hidden" name="__VIEWSTATE2" value="0351118MK" /><input type="hidden" name="__VIEWSTATE3" value="ZVVV91yjY" />

对于 的随机顺序,你可以用这个:typenamevalue

<[ ]*input[ ]*[A-Za-z ="_0-9+/]*>

<[ ]*input[ ]*[A-Za-z ="_0-9+/]*[ ]*[/]>

关于这一点:

<input  name="SaveRequired" type="hidden" value="False" /><input type="hidden" name="__VIEWSTATE1" value="1H4sIAAtzrkX7QfL5VEGj6nGi+nP" /><input type="hidden" name="__VIEWSTATE2" value="0351118MK" /><input  name="__VIEWSTATE3" type="hidden" value="ZVVV91yjY" />

`

顺便说一句,我认为你想要这样的东西:

<[ ]*input(([ ]*type="hidden"[ ]*name=[A-Za-z0-9_+"]*[ ]*value=[A-Za-z0-9_+"]*[ ]*)+)[ ]*/>|<[ ]*input(([ ]*type="hidden"[ ]*value=[A-Za-z0-9_+"]*[ ]*name=[A-Za-z0-9_+"]*[ ]*)+)[ ]*/>|<[ ]*input(([ ]*name=[A-Za-z0-9_+"]*[ ]*type="hidden"[ ]*value=[A-Za-z0-9_+"]*[ ]*)+)[ ]*/>|<[ ]*input(([ ]*value=[A-Za-z0-9_+"]*[ ]*type="hidden"[ ]*name=[A-Za-z0-9_+"]*[ ]*)+)[ ]*/>|<[ ]*input(([ ]*name=[A-Za-z0-9_+"]*[ ]*value=[A-Za-z0-9_+"]*[ ]*type="hidden"[ ]*)+)[ ]*/>|<[ ]*input(([ ]*value=[A-Za-z0-9_+"]*[ ]*name=[A-Za-z0-9_+"]*[ ]*type="hidden"[ ]*)+)[ ]*/>

它不好,但它以任何方式工作。

测试时间:http://regexpal.com/

9赞 Suamere 9/19/2013 #6

虽然我喜欢这些答案的其余部分的内容,但它们并没有真正直接或正确地回答问题。甚至铂金的回答也过于复杂,而且效率也较低。所以我被迫把这个放进去。

如果使用得当,我是正则表达式的忠实拥护者。但是由于污名化(和性能),我总是说格式良好的 XML 或 HTML 应该使用 XML 解析器。更好的性能是字符串解析,尽管如果可读性太失控,则可读性之间有一条线。然而,这不是问题。问题是如何匹配隐藏类型的输入标签。答案是:

<input[^>]*type="hidden"[^>]*>

根据您的风格,您唯一需要包含的正则表达式选项是 ignorecase 选项。

评论

6赞 Ilmari Karonen 1/18/2014
<input type='hidden' name='Oh, <really>?' value='Try a real HTML parser instead.'>
4赞 Suamere 1/31/2014
您的示例是自关闭的。应以 /> 结尾。此外,虽然在名称字段中出现 a 的可能性几乎为零,但确实有可能在操作句柄中出现 a。例如:对 OnClick 属性的内联 javascript 调用。话虽如此,我有一个 XML 解析器,但也有一个正则表达式,用于那些我得到的文档太乱而 XML 解析器无法处理的人,但正则表达式可以。此外,这不是问题所在。你永远不会遇到这些隐藏输入的情况,我的答案是最好的。.>>Ya, <really>!
4赞 Ilmari Karonen 1/31/2014
/>是 XML-ism;它在任何版本的 HTML 中都不是必需的,除了 XHTML(它从未真正获得太多牵引力,并且几乎被 HTML5 取代)。你说得对,那里有很多混乱的、不真正有效的 HTML,但一个好的 HTML(不是 XML)解析器应该能够处理其中的大部分;如果他们不这样做,很可能浏览器也不会。
1赞 Suamere 1/31/2014
如果您唯一需要的解析或搜索是单击以返回隐藏输入字段的集合,那么此正则表达式将是完美的。当内置正则表达式时,使用 .NET XML 文档类或引用第三方 XML/HTML 分析器来调用一个方法都是矫枉过正的。你是对的,一个网站如此混乱,以至于一个好的 HTML 解析器无法处理它,甚至可能不是开发人员会考虑的东西。但是我的公司每个月都会交出数百万个页面,这些页面以多种方式串联和顶起,因此有时(并非总是)正则表达式是最佳选择。
1赞 Suamere 1/31/2014
唯一的一点是,我们不确定整个公司的原因,这个开发人员想要这个答案。但这正是他所要求的。
1赞 HTML5 developer 3/11/2015 #7

我想用它来提取html代码。**DOMDocument**

$dom = new DOMDocument();
$dom ->loadHTML($input);
$x = new DOMXpath($dom );
$results = $x->evaluate('//input[@type="hidden"]');

foreach ( $results as $item) {
    print_r( $item->getAttribute('value') );
}

顺便说一句,你可以在这里测试它 - regex101.com。它实时显示结果。 关于正则表达式的一些规则:http://www.eclipse.org/tptp/home/downloads/installguide/gla_42/ref/rregexp.html Reader

0赞 Nitin9791 3/13/2016 #8

假设您的 HTML 内容存储在字符串 HTML 中,那么为了获取包含隐藏类型的每个输入,您可以使用正则表达式

var regex = /(<input.*?type\s?=\s?["']hidden["'].*?>)/g;
html.match(regex);

上面的正则表达式找到,后跟任意数量的字符,直到它得到或 type='hidden' 后跟任意数量的字符,直到它得到<inputtype="hidden">

/g 告诉正则表达式查找与给定模式匹配的每个子字符串。