适用于类似Gmail的搜索的正则表达式

时间:2012-05-15 10:34:50

标签: .net regex

我一直试图找出类似Gmail的搜索的正则表达式,即:

name:Joe surname:(Foo Bar)

...就像this topic一样。但略有不同:如果有一个没有key:的文本,它也会被拆分,所以:

foo:(hello world) bar:(-{bad things}) some text to search

将返回:

foo:(hello world)
bar:(-{bad things})
some text to search

7 个答案:

答案 0 :(得分:4)

您在使用Regex路线时遇到的问题是遇到空格问题。可能有一个非常复杂的正则表达式,但对于一个简单的正则表达式,你会发现你的搜索不能包含关键字的空格,例如:

  

作品:site:mysite用户:john
  失败:site:“我真棒网站”用户:john

这将失败,因为它是基于空格的标记化。因此,如果需要空间支持,请继续阅读...

我建议使用Lucene .NET引擎的内置解析器为您提供令牌,或使用语法和解析器,如GoldParser,Irony或Antlr。

对于你想要的东西来说听起来可能太长而且复杂,但是为GoldParser写了一个语法来完成你正在做的事情,一旦语法完成,它实际上很容易。这是语法的一个例子:

"Name"     = 'Spruce Search Grammar'
"Version"  = '1.1'
"About"    = 'The search grammar for Spruce TFS MVC frontend'

"Start Symbol" = <Query>

! -------------------------------------------------
! Character Sets
! -------------------------------------------------
{Valid} = {All Valid} - ['-'] - ['OR'] - {Whitespace} - [':'] - ["] - ['']
{Quoted} = {All Valid} - ["] - ['']

! -------------------------------------------------
! Terminals
! -------------------------------------------------
AnyChar    = {Valid}+
Or = 'OR'
Negate = ['-']
StringLiteral   = '' {Quoted}+ '' | '"' {Quoted}+ '"'

! -- Field-specific terms
Project     = 'project' ':'
...
CreatedOn   = 'created-on' ':'
ResolvedOn  = 'resolved-on' ':'
! -------------------------------------------------
! Rules
! -------------------------------------------------

! The grammar starts below
<Query> ::= <Query> <Keywords> | <Keywords>
<SingleWord> ::= AnyChar

<Keywords> ::= <SingleWord>
              | <QuotedString> 
              | <Or> 
              | <Negate> 
              | <FieldTerms>

<Or> ::= <Or> <SingleWord> 
        | Or Negate
        | Or <SingleWord>
        | Or <QuotedString>

<Negate> ::= <Negate> Negate <SingleWord>
            | <Negate> Negate <QuotedString>
            | Negate <SingleWord>
            | Negate <QuotedString>

<QuotedString> ::= StringLiteral

<FieldTerms> ::= <FieldTerms> Project | <FieldTerms> Description | <FieldTerms> State 
                | <FieldTerms> Type | <FieldTerms> Area | <FieldTerms> Iteration 
                | <FieldTerms> AssignedTo | <FieldTerms> ResolvedBy 
                | <FieldTerms> ResolvedOn | <FieldTerms> CreatedOn
                | Project 
                | <Description>
                | State 
                | Type 
                | Area 
                | Iteration 
                | CreatedBy
                | AssignedTo 
                | ResolvedBy
                | CreatedOn
                | ResolvedOn

<Description> ::= <Description> Description | <Description> Description StringLiteral
                | Description | Description StringLiteral

这为您提供以下内容的搜索支持:

  

解决:john项目:“惊人的tfs项目”

如果查看Keywords令牌,您可以看到它期待单字,OR,带引号的字符串或负数(NOT)。当这个定义变为递归时,你会发现这很难,你可以在<Description>部分看到。

语法称为EBNF,它描述了您的语言格式。您可以编写像搜索查询解析器一样简单的东西,或整个计算机语言。 Goldparser解析令牌的方式会限制你,因为它会预测令牌(LALR),因此HTML和Wiki语法等语言会破坏你尝试编写的任何语法,因为这些格式不会强迫你关闭标签/代币。 Antlr为您提供了LL(*),它对丢失的起始标记/标记更加宽容,但对于搜索查询解析器而言您不需要担心。

我的语法和C#代码的代码文件夹可以在project

中找到

QueryParser是解析搜索字符串的类,语法文件是.grm文件,2mb文件是Goldparser如何优化语法以基本创建自己的可能性表。 Calitha是GoldParser的C#库,很容易实现。如果没有编写更大的答案,很难准确描述它是如何完成的,但是一旦你编译了语法就很简单了,Goldparser有一个非常直观的IDE用于编写语法和一大堆现有的语法,如SQL,C#,我相信Java甚至是Perl正则表达式。

这不是一个1小时的快速修复,因为你从正则表达式获得,接近2-3天,但是你确实学习了“正确的”解析方法。

答案 1 :(得分:3)

使用单个正则表达式无法获取所需的一切。问题在于没有可靠的方法来获取非密钥文本。

但是,如果我们首先抓取并存储所有密钥文本,然后使用空字符串进行正则表达式替换(使用相同的正则表达式),我们会突然自己获取搜索字符串!

  1. 使用以下正则表达式(see it on RegExr)抓取关键字和相关文字:

    ([a-zA-Z]+:(?:\([^)]+?\)|[^( ]+))
  2. 然后使用相同的正则表达式使用空字符串对完整搜索字符串执行正则表达式替换。生成的字符串将是非keyworded搜索文本。有点像:

    Regex.Replace(searchtext, @"[a-zA-Z]+:(?:\([^)]+?\)|[^( ]+)", "");
    
  3. 在搜索文本的开头和结尾处执行空格修剪

  4. 从搜索文本中删除双倍(或更多空格)(可以使用正则表达式替换,替换为单个空格):

    Regex.Replace(searchtext, @" {2,}", " ");
                                ^-- notice the space :)
    
  5. ????

  6. PROFIT !!!

  7. 完全有可能在#2的正则表达式中执行空白删除,但在处理正则表达式时,我倾向于保持尽可能干净。

答案 2 :(得分:0)

您可以查看此question.

它包含以下Regex示例:

^((?!hede).)*$ 

作为答案的作者,“上面的正则表达式将匹配任何字符串,或没有换行符的行,不包含(子)字符串'hede'。”

因此,您应该能够将此与您发布的topic以及上述正则表达式中的信息相结合,以解决您的问题。

希望这有帮助!!!

答案 3 :(得分:0)

这可能适合你

在Java中:

p = Pattern.compile("(\\w+:(\\(.*?\\))|.+)\\s*");
m = p.matcher("foo:(hello world) bar:(-{bad things}) some text to search");
while(m.find()){
    Log.v("REGEX", m.group(1));
}

产地:

  
    

05-25 15:21:06.242:V / REGEX(18203): foo :(你好世界)
    05-25 15:21:08.061:V / REGEX(18203): bar :( - {bad things})
    05-25 15:21:09.761:V / REGEX(18203):一些要搜索的文字

  

只要标签是第一个并且自由文本是最后一个,正则表达式就可以工作 即使对于代码,您也可以使用m.group(2)

获取内容

答案 4 :(得分:0)

这里的一个简单方法是将字符串与此模式匹配:

\w+:(?:\([^)]*\)|\S+)|\S+

那将匹配:

  • \w+: - 一把钥匙。
  • (?:) - 接着是......
    • \([^)]*\) - 括号
    • | - 或
    • \S+ - 一些不是空格的字符。
  • |\S+ - 或者只匹配一个单词。

请注意,此模式会将单词分成不同的匹配项。如果您真的无法处理,可以使用|(?:\S+(\s+(?!\w*:)[^\s:]+)*)而不是最后|\S+

工作示例:http://ideone.com/bExFd

答案 5 :(得分:0)

另一个选择,更强大一点:
在这里,我们可以使用.Net模式的一些高级功能 - 它们保留所有组的所有捕获。这是构建完整解析器的有用功能。在这里,我添加了一些其他搜索功能,例如带引号的字符串和运算符(例如OR或范围..):

\A
(?>
    \s                      # skip over spaces.
    |
    (?<Key>\w+):            # Key:
    (?:                     # followed by:
        \(                     
        (?<KeyValue>[^)]*)      # Parentheses
        \)
        |                       # or
        (?<KeyValue>\S+)        # a single word
    )
    |
    (?<Operator>OR|AND|-|\+|\.\.)
    |
    ""(?<Term>[^""]*)""     # quoted term
    |
    (?<Term>\w+)            # just a word
    |
    (?<Invalid>.)           # Any other character isn't valid
)*
\z

您现在可以轻松获取所有令牌及其位置(您还可以压缩Key和KeyValue捕获以配对它们):

Regex queryParser = new Regex(pattern, RegexOptions.IgnorePatternWhitespace);
Match m = queryParser.Match(query); // single match!
// ...
var terms = m.Groups["Term"].Captures;

工作示例:http://ideone.com/B7tln

答案 6 :(得分:0)

您只需使用一个正则表达式即可解决此问题。您可以重复使用您指示部分有效的you linked to的答案。

最后一个数组元素是唯一需要纠正的元素。

使用您最初获得的示例:

[
    "foo:(hello world)",
    "bar:(-{bad things}) some text to search"
]

最后一项需要拆分为文本,包括第一个右括号和后面的文本。然后,您将最后一项替换为包含括号的文本,然后将其后面的文本追加到数组中。

[
    "foo:(hello world)",
    "bar:(-{bad things})",
    "some text to search"
]

以下伪代码应说明如何做到这一点:

array; // Array returned when string was split using /\s+(?=\w+:)/
lastPosition = array.length-1;

lastElem = array[lastPosition]; // May contain text without a key

// Key is followed by an opening bracket
//  (check for opening bracket after semi-colon following key)
if ( lastElem.match( /^[^:]*:(/ ) ) {
    // Need to replace array entry with key and all text up to and including
    // closing bracket.
    // Additional text needs to be added to array.

    maxSplitsAllowed = 1;
    results = lastElem.split( /)\w*/ , maxSplitsAllowed );
    // White space following the bracket was included in the match so it
    //  wouldn't be at the front of the text without a key

    lastKeyAndText = results[0] + ')'; // Re-append closing bracket
    endingTextWithoutKey = results[1];

    array[lastPosition] = lastKeyAndText; // Correct array entry for last key
    array.append( endingTextWithoutKey ); // Append text without key

// Key is not followed by a closing bracket but has text without a key
//  (check for white space following characters that aren't white space
//   characters)
} else if (lastElem.match( /^[^:]*:[^\w]*\w/ )) {
    // Need to change array entry so that all text before first space
    // becomes the key.
    // Additional text needs to be added to array.

    maxSplitsAllowed = 1;
    results = lastElem.split( /\w+/ , maxSplitsAllowed );

    lastKeyAndText = results[0];
    endingTextWithoutKey = results[1];

    array[lastPosition] = lastKeyAndText; // Correct array entry for last key
    array.append( endingTextWithoutKey ); // Append text without key
}

我认为当空格字符包含在密钥后面的文本中时,需要使用括号。