Erlang手册re模块翻译(二)——inspect/run函数

前端之家收集整理的这篇文章主要介绍了Erlang手册re模块翻译(二)——inspect/run函数前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
inspect(MP,Item) -> {namelist,[binary()]}
    类型:
        MP = mp()
        Item = namelist
    该函数编译一个正则表达式和一个项目,从正则表达式中返回相关的数据。目前唯一支持的项目为namelist,它返回一个元组{namelist,[binary()]},包含正则表达式中所有的命名子模式中的名字(惟一的)。
    示例:

    1> {ok,MP} = re:compile("(?<A>A)|(?<B>B)|(?<C>C)").
        {ok,{re_pattern,3,0,<<69,82,67,80,119,1,255,...>>}}
    2> re:inspect(MP,namelist).
        {namelist,[<<"A">>,<<"B">>,<<"C">>]}
    3> {ok,MPD} = re:compile("(?<C>A)|(?<B>B)|(?<C>C)",[dupnames]).
        {ok,8,...>>}}
    4> re:inspect(MPD,namelist).                                   
        {namelist,[<<"B">>,<<"C">>]}

    特别注意,第二个示例中相同的名字再返回列表中只出现了一次,并且在列表中名字是按字母顺序排序的而不论该名字在正则表达式的位置。如果在re:run/3中设置了{capture,all_names}选项,则返回列表的名字顺序就和正则表达式中捕获子表达式的顺序一样了。因此你可以从re:run/3的返回结果来创建一个名称到值的映射。就像下面这样:

    1> {ok,...>>}}
    2> {namelist,N} = re:inspect(MP,<<"C">>]}
    3> {match,L} = re:run("AA",MP,[{capture,all_names,binary}]).
        {match,<<>>,<<>>]}
    4> NameMap = lists:zip(N,L).
        [{<<"A">>,<<"A">>},{<<"B">>,<<>>},{<<"C">>,<<>>}]

    更多选项预计会在以后添加
run(Subject,RE) -> {match,Captured} | nomatch
    类型:
        Subject = iodata() | unicode:charlist()
        RE = mp() | iodata()
        Captured = [CaptureData]
        CaptureData = {integer(),integer()}
    执行一个正则匹配,匹配成功则返回 {match,Captured},否则返回一个 nomatch 的原子。参数 RE 可以是正则字符串,也可以是用 re:compile 预编译过的正则匹配指令。返回的 Captured 值包含匹配结果的开始的位置和匹配结果的长度,例如下面返回的是 [{4,4}],表示匹配结果在第 4 个字符后出现,匹配结果值的长度是 41>re:run("abc 1234 @#$","\\d+").
        {match,[{4,4}]}
    2> re:run("abcdefg","abc").
        {match,[{0,3}]}

    效果等价于run(Subject,RE,[]).
run(Subject,Options) ->
       {match,Captured} | match | nomatch | {error,ErrType}
    类型:
        Subject = iodata() | unicode:charlist()
        RE = mp() | iodata() | unicode:charlist()
        Options = [Option]
        Option = anchored
               | global
               | notbol
               | noteol
               | notempty
               | notempty_atstart
               | report_errors
               | {offset,integer() >= 0}
               | {match_limit,integer() >= 0}
               | {match_limit_recursion,integer() >= 0}
               | {newline,NLSpec :: nl_spec()}
               | bsr_anycrlf
               | bsr_unicode
               | {capture,ValueSpec}
               | {capture,ValueSpec,Type}
               | CompileOpt
        Type = index | list | binary
        ValueSpec = all
                  | all_but_first
                  | all_names
                  | first
                  | none
                  | ValueList
        ValueList = [ValueID]
        ValueID = integer() | string() | atom()
        CompileOpt = compile_option()
        See compile/2 above.
        Captured = [CaptureData] | [[CaptureData]]
        CaptureData = {integer(),integer()}
                    | ListConversionData
                    | binary()
        ListConversionData = string()
                           | {error,string(),binary()}
                           | {incomplete,binary()}
        ErrType = match_limit
                | match_limit_recursion
                | {compile,CompileErr}
        CompileErr = 
            {ErrString :: string(),Position :: integer() >= 0}

    执行一个正则匹配,匹配成功则返回 {match,Captured},否则返回一个 nomatch 的原子。参数 RE 可以是正则字符串,也可以是用 re:compile/1 或 re:compile/2 预编译过的正则匹配指令。参数 Options 是一个匹配选项参数。

    如果编译正则表达式时发生了异常错误,只会返回一个 badarg 的提示错误的详细定位信息可以通过 re:compile/2 方法来查看.

    如果RE参数是预编过的正则匹配指令,那么option列表里只能使用以下选项:anchored,global,notbol,noteol,report_errors,notempty,notempty_atstart,{offset,integer() >= 0},{match_limit,{match_limit_recursion,{newline,NLSpec} 和 {capture,ValueSpec}/{capture,Type}.否则如果RE参数是正则字符串,所有对re:compile/2函数有效的选项都可以使用。当编译和执行匹配都能使用的名为anchored和{newline,NLSpec}的选项出现在没有预编译的正则字符串中时,编译和执行都将会受其影响。

    如果正则表达式以选项unicode预编译过,应该提供一个有效的Unicode charlist()类型的Subject,否则任何iodata()类型的也可以。如果涉及编译并且选项为unicode,那么Subject和正则字符串都应该为有效的Unicode charlists()类型。

    {capture,ValueSpec}/{capture,ValueSpec,Type}选项定义了当匹配成功时函数应该返回什么。capture元组可能包含一个值来指明哪一个captured子模式将被返回,和一个类型说明来指明captured子模式该如何返回(以index元组,列表或二进制的形式)。capture选项使得函数变得灵活和强大。不同的选项在下面有详细的描述。

    如果capture选项没有子捕获要完成的选项的描述(即{capture,none}),函数在匹配成功后仅返回简单的原子match,否则返回一个元组{match,ValueList}。禁用capture可以通过指定none或者一个空的列表ValueSpec来完成。

    report_errors选项使得可能返回一个错误元组。该元组将指明一个匹配错误(match_limit或match_limit_recrusion)或一个编译错误错误元组有特定的格式{error,{compile,CompileErr}}。注意如果没有report_errors选项,函数永远不会返回错误元组,但是会把编译错误报告成一个badarg异常,并且使匹配由于超出限制而匹配失败,返回nomatch。
和执行有关的选项有:
    anchored
        限制re:run/3函数在第一个匹配位置进行匹配。如果一个模式是以anchored选项编译的,或者因为优化其内容而转换为anchored,在匹配时不能作为unanchored,因此没有unanchored选项。
            1> re:run("abcdefabcdef","abc",[anchored]).
            {match,[{0,3}]}
            2> re:run("abcdefabcdef",[anchored,{offset,1}]).
            nomatch
            3> re:run("abcdefabcdef",[{offset,1}]).
            {match,[{6,3}]}
            4> re:run("abcdefabcdef",1},gobal]).
             nomatch
        可以看出anchored的作用是在当前匹配位置开始匹配,如果当前位置匹配不成功就不继续进行后面的匹配。
global
        实现全局(重复)搜索(Perl中的g标志)。每个匹配项被当做一个包含匹配内容和匹配子表达式(或者由capture选项指定)的独立的列表返回。返回值的Captured部分因此成为列表的列表,当这个选项被设置时。
        一个有global选项的正则表达式匹配一个空字符串的行为可能使一些用户吃惊。当global选项设置后,re:run/3处理空字符串和Perl处理方式一样:在任何位置的长度为0的匹配都会以[anchored,notempty_atstart]选项重试。如果搜索的结果长度大于0,则该结果被包含。例如:
            re:run("cat","(|at)",[global]).
        以下过程将被执行:
        At offset 0
            正则表达式(|at)首先会在字符串cat的初始位置'c'进行匹配,结果为[{0,0},{0,0}] (第二个 {0,0}是因为由括号标注的子表达式产生的)。 由于结果长度为0,我们不进行到一个位置。
        At offset 0 with [anchored,notempty_atstart]
            搜索会以[anchored,notempyt_atstart]选项在相同的位置重试,这也不会给出更长的结果,所以搜索位置移到下一个字符a。
        At offset 1
            这次,搜索结果为[{1,{1,0}],所以搜索仍会以额外的参数重试。
        At offset 1 with [anchored,notempty_atstart]
            Now the ab alternative is found并且结果是[{1,2},2}]。该结果被加入结果列表,并且搜索字符串的位置也移动两步。
        At offset 3搜索再一次匹配到空字符串,[{3,{3,0}]。
        At offset 1 with [anchored,notempty_atstart]
            这次没有长度大于0的结果,并且我们已经在最后一个位置,所以这次全局搜索已经完成。
        该调用的结果为:
            {match,[[{0,0},{0,0}],[{1,{1,2},2}],[{3,{3,0}]]}
        1> re:run("catcatcat","at",[global]).
        {match,[[{1,[{7,2}]]}
        2> re:run("catcatcat","at").
        {match,[{1,2}]}
        3> re:run("catcatcat",all,list}]).
        {match,["at"]}
        4> re:run("catcatcat",list},global]).
        {match,[["at"],["at"],["at"]]}
        从以上例子可以看出global会重复搜索所有的组合,找到所有的匹配项。
notempty
        如果该选项设置则空字符串不被认为是有效的匹配。如果在模式中有alternatives,它们会被尝试。如果所有的alternatives都匹配到空字符串,则整个匹配失败。例如,如果模式:a?b? 被用于一个不是以a或者b开头的字符串,它将会这这个subject的起始位置匹配到空字符串。有notempty选项,这个匹配就是无效的,所以re:run/3会进一步搜索字符串中出现的a或者b字符。
            1> re:run("abcdefg","d",[notempty]).
            {match,[{3,1}]}
            2> re:run("abcdefg","d").
            {match,1}]}
            3> re:run("abcdefg","d?").
            {match,0}]}
            4> re:run("abcdefg","d?",1}]}
        在上一节中说过问号的作用,具体我也不是很懂,但是问号会使得问号前的那个字符可以不用匹配。在这里d?表示d和一个空字符,所以在和abc匹配时都是匹配到空字符,当没有notempty选项时,空字符有效,所以结果为{match,[{00}]},即示例3;而有notempty选项时,空字符匹配无效,所以会继续向后匹配,知道匹配到d,所以结果为{match,[{31}]},即示例4.
notempty_atstart
        该选项和notempty比较相似,除了不在subject的开始位置的空字符串匹配是允许的。如果模式是anchored,只有该模式包含\K才会发生这样的匹配。
        Perl中没有与notempty或notempty_atstart直接等价的选项,但是当使用/g修饰时,它在自身的split()函数内设置了一种特殊的模式匹配空字符串。在匹配到一个空字符串后,它可以模仿Perl的行为首先在相同的位置用notempty_atstart和anchored选项再次匹配,然后如果失败,推进偏移位置(见下文)然后在尝试一个普通的模式匹配。  
            1> re:run("abcdefg",[notempty_atstart]).
            {match,0}]}
            2> re:run("abcdefg","d?").
            {match,0}]}
        结合上一个选项的说明,在匹配ab时都匹配到了空字符,但是notempty_atstart选项除了subject开始位置的空字符以外,其它的空字符当做有效匹配,所以匹配到的第一个空字符(匹配a得到的)无效,第二个(匹配b得到的)有效,所以结果为{match,[{1,0]},即示例1
notbol
        该选项指明目标字符串的第一个字符不是一行的起始位置,所以^元字符不应该在它之前匹配。设置该选项不设置multiline(在编译时)会导致circumflex永远不匹配。该选项只影响^元字符的行为。不影响\A。
            1> re:run("abcdefg","^a").
            {match,1}]}
            2> re:run("abcdefg","^a",[notbol]).
            nomatch
            3> re:run("abcdefg\nabcdef",1}]).
            nomatch
            4> re:run("abcdefg\nabcdef",multiline]).
            {match,[{8,1}]}
            5> re:run("abcdefg\nabcdef",multiline,notbol]).
            {match,1}]}         
            6> re:run("abcdefg\nabcdef",notbol]).
            nomatch
            7> re:run("abcdefg\nabcdef",[multiline]).
            {match,1}]}
        从上述示例可以看出notbol选项会指明a不是一行的行首,所以示例2不匹配,示例3中虽然有换行符,但是没有设置multiline选项,所以不匹配,当设置了multiline选项,同时设置offset选项跳过第一个a就会匹配第二行开头的a,其中\n代表换行符。其他情况自己分析吧。
noteol
        该选项指明目标字符串的最后一个字符不是行尾,所以$元字符不应该在它之前立即匹配它(除非在multiline模式)或者匹配一个换行符。设置该选项,不设置multiline(编译时)导致$永远不会匹配。该选项仅仅影响$元字符的行为。不影响\Z或者\z。
            1> re:run("abcdef","f$").
            {match,[{5,1}]}
            2> re:run("abcdef","f$",[noteol]).
            nomatch
            3> re:run("abcdef\nffff",[noteol]).
            nomatch
            4> re:run("abcdef\nffff",[noteol,multiline]).
            {match,1}]}
        分析和上一个选项基本相同,所以不做过多解释。
report_errors
        该选项对re:run/3错误处理提供更好地控制。当该选项被设置,编译错误(如果该正则表达式没有预编译)和运行时错误会被显示的返回为一个错误元组。
        可能的运行时错误有:
        match_limit
            PCRE库对内部匹配函数调用次数设置了一个限制。在Erlang编译的库中默认的值为10000000.如果返回{error,match_limit},它表示正则表达式的执行次数达到了这个限制。通常这会被当做nomatch,当发生这种情况时默认返回nomatch,但是通过设置report_errors,在由内部调用超过限制造成匹配失败时会给你一个通知。
        match_limit_recursion
            该错误和match_limit非常相似,但是发生在当PCRE内部函数递归调用次数超过match_limit_recursion限制,该值默认为10000000.需要注意的是只要match_limit和match_limit_default的值保持在默认值以内,match_limit_recursion错误就不会发生,也就是说match_limit错误会发生在它之前(每一次递归调用也是一次调用,但反过来不是)。两个限制都可以被改变,可以通过直接在正则表达式中设置限制值或者在re:run/3函数的选项中给出都可以。
        理解这里说的限制匹配的”递归“不论是在erlang机器的C堆栈中或是在Erlang的进程栈中都不是真正的递归这点是很重要的。在Erlang虚拟机中编译的PCRE库版本使用”堆“来保存正则表达式中在递归调用时需要保存的数值。
{match_limit,integer() >= 0}
        该选项限制implementation-specific方式匹配的执行时间。它在PCRE文档中以下面的方式描述:
        match_limit字段提供了预防PCRE使用大量资源来运行一个不会匹配成功但有很多种可能性的模式的手段。最典型的例子是使用无限重复嵌套的模式。
        在内部,pcre_exec()使用一个名为match()的函数,该函数被重复的调用(有时是递归的)。由match_limit设置的限制应用于该函数在一次匹配中被调用次数,它的作用是限制大量的回溯过程。对于那些非anchored的模式,这个数字在目标字符串中的每个位置都会从0重新开始。
        这意味着失控的正则表达式可以更快的失败如果使用这个选项降低这个限制。在Erlang虚拟机中编译的默认值为10000000.
        注意
        该选项不以任何方式影响Erlang虚拟机中”长期运行的BTF“项目。re:run函数总是在间隔期将控制权交回Erlang进程调度表以确保Erlang系统的实时性。
{match_limit_recursion,integer() >= 0}
        该选项限制了implementation-specific方式匹配的执行时间和内存消耗,和match_limit非常相似。它在PCRE文档中以如下方式描述:
            match_limit_recursion字段和match_limit非常相似,但它不是限制match()函数调用次数,而是限制递归的层次。递归调用深度是一个比总调用次数小的数字,因为不是所有的match调用都是递归调用。该限制只有在小于match_limit时才有用。
            递归深度限制限制了机器可以被使用的堆栈,如果PCRE被编译使用堆而不是使用栈,则限制了可以使用的堆内存。
        Erlang虚拟机使用使用了堆内存的PCRE库,当正则匹配发生递归调用时,这就是为什么该选项会限制机器中堆内存的使用而不是C堆栈的使用。    
        原本应该匹配成功的情况,当该选项使用较低的值就可能会导致深度递归失败从而造成匹配失败:
            1> re:run("aaaaaaaaaaaaaz","(a+)*z").
            {match,14},{0,13}]}
            2> re:run("aaaaaaaaaaaaaz","(a+)*z",[{match_limit_recursion,5}]).
            nomatch
            3> re:run("aaaaaaaaaaaaaz",5},report_errors]).
            {error,match_limit_recursion}
        该选项和match_limit选项应该只被用于很少的情况。建议在试图修改这些选项之前先理解它们。
{offset,integer() >= 0}
        从目标字符串给定的偏移位置开始匹配。偏移位置从0开始编号,所以默认为{offset0}(即匹配目标字符串所有的情况)
        之前已经使用过很多次,这里不再举例。
{newline,NLSpec}
        重写目标字符串中默认的换行符(在erlang中为LF(ASCII 10))的定义。
        cr
            换行符由单个字符CR(ASCII 13)表示。
        lf
            换行符有单个字符LF(ASCII 10)(默认选项)表示。
        crlf
            换行符由一个双字符序列CRLF(ASCII 13 ASCII 10)表示。
        anycrlf
            上述三个sequences任何一个都应该能被识别。
        any
            上述三个换行符,附加上Unicode字符序列VT(垂直选项卡,U+000B),FF(formFeed,U+000C),NEL(下一行,U+0085),LS(行分割符,U+2028)以及PS(段落分割符,U+2029)。

        1> re:run("abcdef\nffff",multiline]).
        {match,1}]}        默认的换行符为\n
        2> re:run("abcdef\nffff",cr}]).
        nomatch                    改变默认定义\n就会失效
        3> re:run("abcdefCRffff",cr}]).
        nomatch                    但是cr并不是代表换行符就是CR,具体是什么目前不知道
        4> re:run("abcdef\nffff",lf}]).
        {match,1}]}            改回默认值就和原本结果一样
bsr_anycrlf
        该选项专门指定\R来只匹配cr,lf或crlf sequences,而不是Unicode换行符。(重写编译选项)

    bsr_unicode
        该选项指定\R来匹配所有的Unicode换行符(默认包含crlf等)。
{capture,ValueSpec}/{capture,Type}
        指明哪个captured子字符串被返回和以什么格式返回。默认情况下,返回re:run/3 captures所有匹配的子字符串部分和所有的捕获子表达式(所有的模式都自动捕获)。默认的返回类型是(从0开始)字符串captured部分的indexs,以{offet,Length}对的形式给出(index类型的捕获)。
        一个默认行为的例子如下:
            re:run("ABCabcdABC","abcd",[]).
        作为第一个和仅有的captured字符串,subject中匹配的部分(中间的"abcd")作为一个index对{3,4},字符位置是从0开始的,和offsets中的一样。上述调用的返回结果为:
            {match,4}]}
        另一个(很普遍)的情况是正则表达式匹配subject中所有的内容,如下:
            re:run("ABCabcdABC",".*abcd.*",[]).
        返回值会相应的指出所有的字符串,从0开始的十个字符:{match,10}]}
        如果正则表达式中包含子模式捕获,像下面这样:
            re:run("ABCabcdABC",".*(abcd).*",[]).
        所有匹配的subject和captured子字符串都被捕获:{match,10},{3,4}]}
        完整的匹配模式总是把第一个返回值放在列表中,剩下的子模式按照它们在正则表达式中出现的顺序添加到后面。

        捕获元组建立如下:
        ValueSpec
            指明哪一个captured(子)模式被返回。ValueSpec可以是一个描述一组预定义的返回值的原子,也可以是包含要返回的子模式的indexes或者名字的列表。

            预定义的子模式有:
            all
                所有captured子模式,包含完整匹配的字符串。这是默认设置。
            all_names
                所有在正则表达式中的命名子模式,好像一个所有名字按照字母顺序排列的列表。该列表中的名字也可以通过inspect/2函数检索。
            first
                仅仅返回第一个captured子模式,这总是完全匹配subject的部分。所有explicitly captured子模式将被丢弃。
            all_but_first
                除过第一个匹配子模式的所有匹配子模式,即所有explicitly captured子模式,但不包含目标字符串中完全匹配的部分。在正则表达式作为一个整体去匹配一个subject很大的一部分,而你感兴趣的部分在一个explicitly captured子模式中时,这是很有用的。如果返回类型是list或者binary,不返回你不感兴趣的子模式是一种很好的优化方式。
            none
                所有匹配子模式都不返回,而是返回单个的原子match代替{match,list()}作为成功匹配时的返回值。指明一个空的列表给出相同的行为。
            值列表是返回的子模式的indexes的列表,0下标是所有的模式,1下标是正则表达式中第一个explicit捕获模式,等等。当在正则表达式中使用命名的captured子模式(见下文),可以使用atom()s或者string()s来指明要返回的子模式。例如,考虑以下正则表达式:
                ".*(abcd).*"
            匹配字符串"ABCabcdABC",只捕获到"abcd"部分(第一个explicit子模式):
                re:run("ABCabcdABC",[{capture,[1]}]).
            该调用将产生以下结果:
                {match,4}]}
            由于第一个explicitly captured子模式是"(abcd)",匹配subject中的"abcd",在(从0开始)第3个位置,长度为4。
            现在考虑相同的正则表达式,但是有explicitly命名为'FOO'的子模式:
                ".*(?<FOO>abcd).*"
            该表达式,我们仍可以用下面的调用给出子模式的index:
                re:run("ABCabcdABC",".*(?<FOO>abcd).*",[1]}]).
            结果和之前一样。但是由于子模式命名了,我们也可以在值列表中指明它的名字。
                re:run("ABCabcdABC",['FOO']}]).
            这个会和早先的例子产生相同的结果,名为:
                {match,4}]}
            值列表可能会指定当前正则表达式不存在的indexs或者名字,这种情况返回值依赖于类型。如果类型是index,正则表达式中没有相应的子模式匹配就会返回一个{-1,0}元组作为值,但是对于其它类型(二进制和列表),返回值分别是空二进制和空列表。
        Type
            指明captured子字符串如何被返回的选项。如果省略,默认使用index。Type可以是以下其中之一:
            index
                返回的captured子字符串包含匹配结果在目标字符串中的位置和匹配结果的长度(就像在匹配前目标字符串使用iolist_to_binary/1或unicode:characters_to_binary/2进行了扁平化处理)。注意unicode选项导致面向字节的indexes在UTF-8编码的二进制中(可能是虚拟的)。当unicode选项起作用时,一个字节index元组{0,2}可能因此表示一个或两个字符。这可能违反直觉,但是一直是公认的最有效和最有用的解决方法。如果需要返回列表而不是简单的代码。类型应该设为该选项。
            list
                把匹配的子字符串作为字符清单返回(Eralng的string()s类型)。如果unicode选项和\C sequence同时在正则表达式中使用,captured子模式可能包含无效的UTF-8字节(\C匹配字节时不管字符的编码)。这种情况,列表捕获可能导致相同类型的元组unicode:characters_to_list/2返回,即不完整或错误的三元组,在成功转换的字符和转换为二进制的无效的UTF-8结尾。最好的策略是避免在捕获列表时使用\C sequence。
            binary
                把匹配子字符串作为二进制返回。如果使用了unicode选项,这些二进制会用UTF-8编码。如果\C序列和unicode一起使用,那么该二进制可能是无效的UTF-8.

        一般来说,在匹配中没有分配值的子模式当type值是index时会返回{-1,0}元组。未分配的子模式对于其它类型分别返回空的二进制或空列表。考虑以下表达式:
            ".*((?<FOO>abdd)|a(..d)).*"
        有三种explicitly捕获子模式,左括号的位置决定了结果的顺序,因此((?<FOO>abdd)|a(..d))是subpattern index 1,(?<FOO>abdd)是subpattern index 2,(..d)是subpattern index 3。当匹配以下字符串:
            "ABCabcdABC"
        subpattern index 2不会匹配,"abdd"不在字符串中,但是完全匹配会匹配(因为alternative a(..d))。subpattern index 2因此没有匹配,默认的返回值是:
            {match,4},{-1,0},{4,3}]}
        把capture类型设置为二进制将会是以下结果:
            {match,[<<"ABCabcdABC">>,<<"abcd">>,<<"bcd">>]}
        其中,空二进制(<<>>)表示未分配的子模式。在二进制的情况下,一些关于匹配的信息因此丢失,<<>>也可能是捕获到的空字符串。
        如果区别空匹配和不存在的子模式是必要的,那么使用index类型并且在Erlang代码中对最终的结果做转换。
        当设置了global选项,capture说明影响每个匹配,所以:
            re:run("cacb","c(a|b)",[global,{capture,[1],list}]).
        得到结果:
            {match,[["a"],["b"]]}
    该选项仅仅影响re:compile/2中描述的编译步骤。
原文链接:https://www.f2er.com/regex/360756.html

猜你在找的正则表达式相关文章