【问题标题】:Regex to match specific functions and their arguments in files正则表达式匹配文件中的特定函数及其参数
【发布时间】:2014-11-19 14:46:37
【问题描述】:

我正在使用 gettext javascript 解析器,但我被困在解析正则表达式上。

我需要捕获传递给特定方法调用_n(_( 的每个参数。例如,如果我的 javascript 文件中有这些:

_("foo") // want "foo"
_n("bar", "baz", 42); // want "bar", "baz", 42
_n(domain, "bux", var); // want domain, "bux", var
_( "one (optional)" ); // want "one (optional)"
apples === 0 ? _( "No apples" ) : _n("%1 apple", "%1 apples", apples) // could have on the same line two calls.. 

这引用了这个文档:http://poedit.net/trac/wiki/Doc/Keywords

我计划做两次(和两个正则表达式):

  1. 捕获_n(_( 方法调用的所有函数参数
  2. 只抓细绳

基本上,我想要一个正则表达式,它可以说“捕获 _n(_( 之后的所有内容,并在函数完成时在最后一个括号 ) 处停止。我不知道如果可以使用正则表达式且无需 javascript 解析器。

还可以做的是“捕获_n(_( 之后的每个“字符串”或“字符串”,并在行尾或新_n(_( 字符的开头停止.

在我所做的所有事情中,我要么卡在带有内括号的_( "one (optional)" );,要么卡在apples === 0 ? _( "No apples" ) : _n("%1 apple", "%1 apples", apples),同时在同一行进行了两次调用。

这是我迄今为止使用不完美的正则表达式实现的:generic parserjavascript onehandlebars one

【问题讨论】:

  • 您说您正在做一个 JS 解析器,但您尝试的正则表达式是 PCRE(它与 JS 不兼容,因为它使用了后视)。您打算使用哪种正则表达式风格?
  • 嗨 Lucas,它是一个用于 javascript 文件的 PHP 解析器。所以是的,正则表达式是 PCRE。最佳
  • 好的,但无论哪种方式,您都应该使用 JS 解析器,因为 you("will", encounter("unexpected", "code") || "patterns" /* or */ + "comments") 在真实代码中。用正则表达式处理这将是一个不必要的痛苦。
  • 该模式是否考虑了 eval(..) 语句的字符串内或 cmets 内的函数?

标签: regex parsing pcre


【解决方案1】:

注意: Read this answer 如果您不熟悉递归。

第 1 部分:匹配特定功能

谁说正则表达式不能模块化?好吧 PCRE 正则表达式来救援!

~                      # Delimiter
(?(DEFINE)             # Start of definitions
   (?P<str_double_quotes>
      (?<!\\)          # Not escaped
      "                # Match a double quote
      (?:              # Non-capturing group
         [^\\]         # Match anything not a backslash
         |             # Or
         \\.           # Match a backslash and a single character (ie: an escaped character)
      )*?              # Repeat the non-capturing group zero or more times, ungreedy/lazy
      "                # Match the ending double quote
   )

   (?P<str_single_quotes>
      (?<!\\)          # Not escaped
      '                # Match a single quote
      (?:              # Non-capturing group
         [^\\]         # Match anything not a backslash
         |             # Or
         \\.           # Match a backslash and a single character (ie: an escaped character)
      )*?              # Repeat the non-capturing group zero or more times, ungreedy/lazy
      '                # Match the ending single quote
   )

   (?P<brackets>
      \(                          # Match an opening bracket
         (?:                      # A non capturing group
            (?&str_double_quotes) # Recurse/use the str_double_quotes pattern
            |                     # Or
            (?&str_single_quotes) # Recurse/use the str_single_quotes pattern
            |                     # Or
            [^()]                 # Anything not a bracket
            |                     # Or
            (?&brackets)          # Recurse the bracket pattern
         )*
      \)
   )
)                                 # End of definitions
# Let's start matching for real now:
_n?                               # Match _ or _n
\s*                               # Optional white spaces
(?P<results>(?&brackets))         # Recurse/use the brackets pattern and put it in the results group
~sx

s 用于将换行符与. 匹配,x 修饰符用于我们正则表达式的这种花哨的间距和注释。

Online regex demo Online php demo

第 2 部分:去掉左括号和右括号

由于我们的正则表达式也会得到左括号和右括号(),我们可能需要过滤它们。我们将在结果上使用preg_replace()

~           # Delimiter
^           # Assert begin of string
\(          # Match an opening bracket
\s*         # Match optional whitespaces
|           # Or
\s*         # Match optional whitespaces
\)          # Match a closing bracket
$           # Assert end of string
~x

Online php demo

第 3 部分:提取参数

这是另一个模块化的正则表达式,您甚至可以添加自己的语法:

~                      # Delimiter
(?(DEFINE)             # Start of definitions
   (?P<str_double_quotes>
      (?<!\\)          # Not escaped
      "                # Match a double quote
      (?:              # Non-capturing group
         [^\\]         # Match anything not a backslash
         |             # Or
         \\.           # Match a backslash and a single character (ie: an escaped character)
      )*?              # Repeat the non-capturing group zero or more times, ungreedy/lazy
      "                # Match the ending double quote
   )

   (?P<str_single_quotes>
      (?<!\\)          # Not escaped
      '                # Match a single quote
      (?:              # Non-capturing group
         [^\\]         # Match anything not a backslash
         |             # Or
         \\.           # Match a backslash and a single character (ie: an escaped character)
      )*?              # Repeat the non-capturing group zero or more times, ungreedy/lazy
      '                # Match the ending single quote
   )

   (?P<array>
      Array\s*
      (?&brackets)
   )

   (?P<variable>
      [^\s,()]+        # I don't know the exact grammar for a variable in ECMAScript
   )

   (?P<brackets>
      \(                          # Match an opening bracket
         (?:                      # A non capturing group
            (?&str_double_quotes) # Recurse/use the str_double_quotes pattern
            |                     # Or
            (?&str_single_quotes) # Recurse/use the str_single_quotes pattern
            |                     # Or
            (?&array)             # Recurse/use the array pattern
            |                     # Or
            (?&variable)          # Recurse/use the array pattern
            |                     # Or
            [^()]                 # Anything not a bracket
            |                     # Or
            (?&brackets)          # Recurse the bracket pattern
         )*
      \)
   )
)                                 # End of definitions
# Let's start matching for real now:
(?&array)
|
(?&variable)
|
(?&str_double_quotes)
|
(?&str_single_quotes)
~xis

我们将循环使用preg_match_all()。最终代码如下所示:

$functionPattern = <<<'regex'
~                      # Delimiter
(?(DEFINE)             # Start of definitions
   (?P<str_double_quotes>
      (?<!\\)          # Not escaped
      "                # Match a double quote
      (?:              # Non-capturing group
         [^\\]         # Match anything not a backslash
         |             # Or
         \\.           # Match a backslash and a single character (ie: an escaped character)
      )*?              # Repeat the non-capturing group zero or more times, ungreedy/lazy
      "                # Match the ending double quote
   )

   (?P<str_single_quotes>
      (?<!\\)          # Not escaped
      '                # Match a single quote
      (?:              # Non-capturing group
         [^\\]         # Match anything not a backslash
         |             # Or
         \\.           # Match a backslash and a single character (ie: an escaped character)
      )*?              # Repeat the non-capturing group zero or more times, ungreedy/lazy
      '                # Match the ending single quote
   )

   (?P<brackets>
      \(                          # Match an opening bracket
         (?:                      # A non capturing group
            (?&str_double_quotes) # Recurse/use the str_double_quotes pattern
            |                     # Or
            (?&str_single_quotes) # Recurse/use the str_single_quotes pattern
            |                     # Or
            [^()]                 # Anything not a bracket
            |                     # Or
            (?&brackets)          # Recurse the bracket pattern
         )*
      \)
   )
)                                 # End of definitions
# Let's start matching for real now:
_n?                               # Match _ or _n
\s*                               # Optional white spaces
(?P<results>(?&brackets))         # Recurse/use the brackets pattern and put it in the results group
~sx
regex;


$argumentsPattern = <<<'regex'
~                      # Delimiter
(?(DEFINE)             # Start of definitions
   (?P<str_double_quotes>
      (?<!\\)          # Not escaped
      "                # Match a double quote
      (?:              # Non-capturing group
         [^\\]         # Match anything not a backslash
         |             # Or
         \\.           # Match a backslash and a single character (ie: an escaped character)
      )*?              # Repeat the non-capturing group zero or more times, ungreedy/lazy
      "                # Match the ending double quote
   )

   (?P<str_single_quotes>
      (?<!\\)          # Not escaped
      '                # Match a single quote
      (?:              # Non-capturing group
         [^\\]         # Match anything not a backslash
         |             # Or
         \\.           # Match a backslash and a single character (ie: an escaped character)
      )*?              # Repeat the non-capturing group zero or more times, ungreedy/lazy
      '                # Match the ending single quote
   )

   (?P<array>
      Array\s*
      (?&brackets)
   )

   (?P<variable>
      [^\s,()]+        # I don't know the exact grammar for a variable in ECMAScript
   )

   (?P<brackets>
      \(                          # Match an opening bracket
         (?:                      # A non capturing group
            (?&str_double_quotes) # Recurse/use the str_double_quotes pattern
            |                     # Or
            (?&str_single_quotes) # Recurse/use the str_single_quotes pattern
            |                     # Or
            (?&array)             # Recurse/use the array pattern
            |                     # Or
            (?&variable)          # Recurse/use the array pattern
            |                     # Or
            [^()]                 # Anything not a bracket
            |                     # Or
            (?&brackets)          # Recurse the bracket pattern
         )*
      \)
   )
)                                 # End of definitions
# Let's start matching for real now:
(?&array)
|
(?&str_double_quotes)
|
(?&str_single_quotes)
|
(?&variable)
~six
regex;

$input = <<<'input'
_  ("foo") // want "foo"
_n("bar", "baz", 42); // want "bar", "baz", 42
_n(domain, "bux", var); // want domain, "bux", var
_( "one (optional)" ); // want "one (optional)"
apples === 0 ? _( "No apples" ) : _n("%1 apple", "%1 apples", apples) // could have on the same line two calls..

// misleading cases
_n("foo (")
_n("foo (\)", 'foo)', aa)
_n( Array(1, 2, 3), Array(")",   '(')   );
_n(function(foo){return foo*2;}); // Is this even valid?
_n   ();   // Empty
_ (   
    "Foo",
    'Bar',
    Array(
        "wow",
        "much",
        'whitespaces'
    ),
    multiline
); // PCRE is awesome
input;

if(preg_match_all($functionPattern, $input, $m)){
    $filtered = preg_replace(
        '~          # Delimiter
        ^           # Assert begin of string
        \(          # Match an opening bracket
        \s*         # Match optional whitespaces
        |           # Or
        \s*         # Match optional whitespaces
        \)          # Match a closing bracket
        $           # Assert end of string
        ~x', // Regex
        '', // Replace with nothing
        $m['results'] // Subject
    ); // Getting rid of opening & closing brackets

    // Part 3: extract arguments:
    $parsedTree = array();
    foreach($filtered as $arguments){   // Loop
        if(preg_match_all($argumentsPattern, $arguments, $m)){ // If there's a match
            $parsedTree[] = array(
                'all_arguments' => $arguments,
                'branches' => $m[0]
            ); // Add an array to our tree and fill it
        }else{
            $parsedTree[] = array(
                'all_arguments' => $arguments,
                'branches' => array()
            ); // Add an array with empty branches
        }
    }

    print_r($parsedTree); // Let's see the results;
}else{
    echo 'no matches';
}

Online php demo

您可能想要创建一个递归函数来生成一棵完整的树。 See this answer.

您可能会注意到function(){} 部分未正确解析。我会把它作为读者的练习:)

【讨论】:

  • 在其他解决方案中看起来最复杂,所以我假设它是最准确的。但它是否考虑到像// _n(x) 这样的行实际上并不是对函数的调用(由 OP 表示为谓词)?
  • @Grx70 不,但我可以通过使用更高级的工具(如(*SKIP)(*FAIL))来考虑这种情况。 See demo。我也可以为/* */ 写点东西,但现在太忙了:)
【解决方案2】:

试试这个:

(?<=\().*?(?=\s*\)[^)]*$)

live demo

【讨论】:

  • 嗨@Bohemian,我首先以为你救了我的命。不幸的是,我在我的模板中发现,在三元组的同一行中有 2 个翻译是很常见的。我更新了上面的帖子向您展示.. 感谢您的帮助!
  • 这里,没有 $ 它几乎适用于所有人,除了最后一个传统 regex101.com/r/uD9uK1/17 这个这里 ((_|__|_t|_n|gettext|ngettext|dgettext))((.*? )(?=\s*)[^)]*$) 几乎正确地捕捉到最后一个。但只有最后一个。
【解决方案3】:

下面的正则表达式应该可以帮助你。

^(?=\w+\()\w+?\(([\s'!\\\)",\w]+)+\);

查看demo here

【讨论】:

  • 谢谢@Kannan,差不多好了,但是我在你的正则表达式中发现了一个漏洞,如果一个字符串有右括号,它不会工作,请看看这个版本:regex101.com/r/uD9uK1/11 谢谢
  • 我已经更新了也可以匹配右括号的正则表达式。如果您需要正则表达式来匹配更多特殊字符,可以将其添加到该组中([\s'!\\\)",\w]+)
【解决方案4】:

\(( |"(\\"|[^"])*"|'(\\'|[^'])*'|[^)"'])*?\)

这应该得到一对括号之间的任何内容,忽略引号中的括号。 说明:

\( // Literal open paren
    (
         | //Space or
        "(\\"|[^"])*"| //Anything between two double quotes, including escaped quotes, or
        '(\\'|[^'])*'| //Anything between two single quotes, including escaped quotes, or
        [^)"'] //Any character that isn't a quote or close paren
    )*? // All that, as many times as necessary
\) // Literal close paren

无论你如何切片,正则表达式都会导致问题。它们难以阅读、难以维护且效率极低。我对 gettext 不熟悉,但也许你可以使用 for 循环?

// This is just pseudocode.  A loop like this can be more readable, maintainable, and predictable than a regular expression.
for(int i = 0; i < input.length; i++) {
    // Ignoring anything that isn't an opening paren
    if(input[i] == '(') {
        String capturedText = "";
        // Loop until a close paren is reached, or an EOF is reached
        for(; input[i] != ')' && i < input.length; i++) {
            if(input[i] == '"') {
                // Loop until an unescaped close quote is reached, or an EOF is reached
                for(; (input[i] != '"' || input[i - 1] == '\\') && i < input.length; i++) {
                    capturedText += input[i];
                }
            }
            if(input[i] == "'") {
                // Loop until an unescaped close quote is reached, or an EOF is reached
                for(; (input[i] != "'" || input[i - 1] == '\\') && i < input.length; i++) {
                    capturedText += input[i];
                }
            }
            capturedText += input[i];
        }
        capture(capturedText);
    }
}

注意:我没有介绍如何确定它是一个函数还是一个分组符号。 (即,这将匹配a = (b * c))。这很复杂,在here 中有详细介绍。随着您的代码越来越准确,您越来越接近编写自己的 javascript 解析器。如果您需要这种准确性,您可能需要查看实际 javascript 解析器的源代码。

【讨论】:

    【解决方案5】:

    一点代码(你可以在http://writecodeonline.com/php/测试这个PHP代码来检查):

    $string = '_("foo")
    _n("bar", "baz", 42); 
    _n(domain, "bux", var);
    _( "one (optional)" );
    apples === 0 ? _( "No apples" ) : _n("%1 apple", "%1 apples", apples)';
    
    preg_match_all('/(?<=(_\()|(_n\())[\w", ()%]+(?=\))/i', $string, $matches);
    
    foreach($matches[0] as $test){
        $opArr = explode(',', $test);
        foreach($opArr as $test2){
           echo trim($test2) . "\n";
           }
        }
    

    您可以在此处查看初始模式及其工作原理:http://regex101.com/r/fR7eU2/1

    输出是:

    "foo"
    "bar"
    "baz"
    42
    domain
    "bux"
    var
    "one (optional)"
    "No apples"
    "%1 apple"
    "%1 apples"
    apples
    

    【讨论】:

      【解决方案6】:

      我们可以分两步完成:

      1) 捕获 _n( 或 _( 方法调用的所有函数参数

      (?:_\(|_n\()(?:[^()]*\([^()]*\))*[^()]*\)
      

      查看演示。

      http://regex101.com/r/oE6jJ1/13

      2) 只抓粗线

      "([^"]*)"|(?:\(|,)\s*([^"),]*)(?=,|\))
      

      查看演示。

      http://regex101.com/r/oE6jJ1/14

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多