5

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

name:Joe surname:(Foo Bar)

...就像在这个话题中一样。但略有不同:如果有一个没有 a 的文本key:,它也会被拆分,所以:

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

会返回:

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

7 回答 7

4

没有办法使用单个正则表达式来获取您需要的一切。问题是没有可靠的方法来获取非关键字文本。

但是,如果我们先抓取并存储所有的关键字文本,然后用一个空字符串进行正则表达式替换(使用相同的正则表达式),我们会突然得到搜索字符串本身!

  1. 使用以下正则表达式获取关键字和相关文本(参见 RegExr):

    ([a-zA-Z]+:(?:\([^)]+?\)|[^( ]+))
  2. 然后使用空字符串对完整的搜索字符串进行正则表达式替换,使用相同的正则表达式。结果字符串将是非关键字搜索文本。类似于以下内容:

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

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

    Regex.Replace(searchtext, @" {2,}", " ");
                                ^-- 注意空格:)
    
  5. ???

  6. 利润!!!

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

于 2012-05-25T13:54:54.607 回答
4

走正则表达式路线时遇到的问题是您遇到了空格问题。可能有一个非常复杂的正则表达式来执行此操作,但对于一个简单的正则表达式,您会发现您的搜索不能包含关键字空格,例如:

作品:网站:我的网站用户:约翰
失败:网站:“我的真棒网站”用户:约翰

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

我建议要么使用 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# 代码的代码文件夹可以在这个项目中找到。

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

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

于 2012-05-25T11:20:37.650 回答
0

You don't need to solve this problem using only one regular expression. You can re-use the answer that you linked to that you indicated would partially work.

The last array element is the only one that needs to be corrected.

Using your example you'd initially get:

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

The last item needs to be split into text up to and including the first closing bracket and text following it. You'd then replace the last item with the text up to and including the bracket and then you'd append the text following it to the array.

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

The following pseudo code should explain how this can be done:

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
}

I assumed that brackets are required when white space characters are to be included within text that follows a key.

于 2012-05-28T18:42:47.577 回答
0

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

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

那将匹配:

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

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

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

于 2012-05-25T21:54:13.893 回答
0

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

\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

于 2012-05-25T21:57:10.087 回答
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:(hello world)
05-25 15:21:08.061: V/REGEX(18203): bar:(-{坏事})
05-25 15:21:09.761:V/REGEX(18203):一些要搜索的文本

只要标签在前,自由文本在后,正则表达式就可以工作。
即使对于标签,您也可以获取内容m.group(2)

于 2012-05-25T12:29:10.717 回答
0

你可能想看看这个问题。

它包含以下正则表达式示例:

^((?!hede).)*$ 

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

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

希望这可以帮助!!!

于 2012-05-25T11:19:24.840 回答