2013-12-19 2 views
3

У меня есть следующая строка:Каков наилучший способ разобрать эту строку внутри строки?

string fullString = "group = '2843360' and (team in ('TEAM1', 'TEAM2','TEAM3'))" 

И я хочу, чтобы разобрать из этой строки в

string group = ParseoutGroup(fullString); // Expect "2843360" 
string[] teams = ParseoutTeamNames(fullString); // Expect array with three items 

В терминах примера полной строки, я мог бы иметь одну или несколько команд, перечисленных (не всегда три, как указано выше).

У меня это частично работает, но мой код выглядит очень взломанным и не очень перспективным, поэтому я хотел посмотреть, было ли здесь лучшее решение для регулярного выражения или более элегантный способ анализа этих значений из этой полной строки ? Могут быть другие вещи, добавленные позже в строку, поэтому я хочу, чтобы это было как можно более надежным.

+4

Трудно обеспечить лучшее решение, если вы не видите другого –

+2

Почему бы вам не опубликовать текущее решение, и мы можем увидеть его улучшение. – tofutim

+0

«не всегда 3, как указано выше» – tofutim

ответ

4

мне удалось сделать это с помощью regular expressions:

var str = "group = '2843360' and (team in ('TEAM1', 'TEAM2','TEAM3'))"; 

// Grabs the group ID 
var group = Regex.Match(str, @"group = '(?<ID>\d+)'", RegexOptions.IgnoreCase) 
    .Groups["ID"].Value; 

// Grabs everything inside teams parentheses 
var teams = Regex.Match(str, @"team in \((?<Teams>(\s*'[^']+'\s*,?)+)\)", RegexOptions.IgnoreCase) 
    .Groups["Teams"].Value; 

// Trim and remove single quotes 
var teamsArray = teams.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) 
    .Select(s => 
     { 
      var trimmed = s.Trim(); 
      return trimmed.Substring(1, trimmed.Length - 2); 
     }).ToArray(); 

Результат будет:

string[] { "TEAM1", "TEAM2", "TEAM3" } 
+1

Красиво сделано Бруно. – tofutim

6

В простейшем случае правильное выражение может быть лучшим ответом. К сожалению, в данном случае нам нужно разобрать подмножество языка SQL. Хотя это можно решить с помощью регулярных выражений, они не предназначены для синтаксического анализа сложных языков (вложенные скобки и экранированные строки).

Также возможно, что требования будут развиваться с течением времени, и для этого потребуется проанализировать более сложные структуры.

Если политика компании позволяет, я буду выбирать, чтобы построить внутренний DSL, чтобы разобрать эту строку.

Один из моих любимых инструментов для создания внутреннего DLSS называется Sprache

Ниже вы можете найти пример синтаксический анализ с использованием внутреннего DSL подхода.

В коде я определил примитивы для обработки требуемых операторов SQL и составил из них окончательный синтаксический анализатор.

[Test] 
    public void Test() 
    { 
     string fullString = "group = '2843360' and (team in ('TEAM1', 'TEAM2','TEAM3'))"; 


     var resultParser = 
      from @group in OperatorEquals("group") 
      from @and in OperatorEnd() 
      from @team in Brackets(OperatorIn("team")) 
      select new {@group, @team}; 
     var result = resultParser.Parse(fullString); 
     Assert.That(result.group, Is.EqualTo("2843360")); 
     Assert.That(result.team, Is.EquivalentTo(new[] {"TEAM1", "TEAM2", "TEAM3"})); 
    } 

    private static readonly Parser<char> CellSeparator = 
     from space1 in Parse.WhiteSpace.Many() 
     from s in Parse.Char(',') 
     from space2 in Parse.WhiteSpace.Many() 
     select s; 

    private static readonly Parser<char> QuoteEscape = Parse.Char('\\'); 

    private static Parser<T> Escaped<T>(Parser<T> following) 
    { 
     return from escape in QuoteEscape 
       from f in following 
       select f; 
    } 

    private static readonly Parser<char> QuotedCellDelimiter = Parse.Char('\''); 

    private static readonly Parser<char> QuotedCellContent = 
     Parse.AnyChar.Except(QuotedCellDelimiter).Or(Escaped(QuotedCellDelimiter)); 

    private static readonly Parser<string> QuotedCell = 
     from open in QuotedCellDelimiter 
     from content in QuotedCellContent.Many().Text() 
     from end in QuotedCellDelimiter 
     select content; 

    private static Parser<string> OperatorEquals(string column) 
    { 
     return 
      from c in Parse.String(column) 
      from space1 in Parse.WhiteSpace.Many() 
      from opEquals in Parse.Char('=') 
      from space2 in Parse.WhiteSpace.Many() 
      from content in QuotedCell 
      select content; 
    } 

    private static Parser<bool> OperatorEnd() 
    { 
     return 
      from space1 in Parse.WhiteSpace.Many() 
      from c in Parse.String("and") 
      from space2 in Parse.WhiteSpace.Many() 
      select true; 
    } 

    private static Parser<T> Brackets<T>(Parser<T> contentParser) 
    { 
     return from open in Parse.Char('(') 
       from space1 in Parse.WhiteSpace.Many() 
       from content in contentParser 
       from space2 in Parse.WhiteSpace.Many() 
       from close in Parse.Char(')') 
       select content; 
    } 

    private static Parser<IEnumerable<string>> ComaSeparated() 
    { 
     return from leading in QuotedCell 
       from rest in CellSeparator.Then(_ => QuotedCell).Many() 
       select Cons(leading, rest); 
    } 

    private static Parser<IEnumerable<string>> OperatorIn(string column) 
    { 
     return 
      from c in Parse.String(column) 
      from space1 in Parse.WhiteSpace 
      from opEquals in Parse.String("in") 
      from space2 in Parse.WhiteSpace.Many() 
      from content in Brackets(ComaSeparated()) 
      from space3 in Parse.WhiteSpace.Many() 
      select content; 
    } 

    private static IEnumerable<T> Cons<T>(T head, IEnumerable<T> rest) 
    { 
     yield return head; 
     foreach (T item in rest) 
      yield return item; 
    } 
0

Я думаю, вам нужно будет искать в процессе лексического для того, чтобы получить желаемый результат, и принять во внимание порядок выполнения установленного скобкой. Вы можете использовать алгоритм шунтирования, чтобы помочь с символикой и порядком выполнения.

Преимущество шунтирующего двора заключается в том, что он позволяет вам определять токены, которые впоследствии могут использоваться для свойства, анализировать строку и выполнять правильную операцию. Хотя он обычно применяется к математическому порядку операций, он может быть адаптирован к вашей цели.

Вот некоторая информация:

http://en.wikipedia.org/wiki/Shunting-yard_algorithm http://www.slideshare.net/grahamwell/shunting-yard

1

Там в probabl y для этого используется регулярное выражение, но если формат строгий, я сначала пытаюсь использовать эффективные строковые методы. Следующее работает с вашим вводом.

Я использую пользовательский класс, TeamGroup, инкапсулировать сложности и иметь все соответствующие свойства в одном объекте:

public class TeamGroup 
{ 
    public string Group { get; set; } 
    public string[] Teams { get; set; } 

    public static TeamGroup ParseOut(string fullString) 
    { 
     TeamGroup tg = new TeamGroup{ Teams = new string[]{ } }; 
     int index = fullString.IndexOf("group = '"); 
     if (index >= 0) 
     { 
      index += "group = '".Length; 
      int endIndex = fullString.IndexOf("'", index); 
      if (endIndex >= 0) 
      { 
       tg.Group = fullString.Substring(index, endIndex - index).Trim(' ', '\''); 
       endIndex += 1; 
       index = fullString.IndexOf(" and (team in (", endIndex); 
       if (index >= 0) 
       { 
        index += " and (team in (".Length; 
        endIndex = fullString.IndexOf(")", index); 
        if (endIndex >= 0) 
        { 
         string allTeamsString = fullString.Substring(index, endIndex - index); 
         tg.Teams = allTeamsString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) 
          .Select(t => t.Trim(' ', '\'')) 
          .ToArray(); 
        } 
       } 
      } 
     } 
     return tg; 
    } 
} 

Вы бы использовать его таким образом:

string fullString = "group = '2843360' and (team in ('TEAM1', 'TEAM2','TEAM3'))"; 
TeamGroup tg = TeamGroup.ParseOut(fullString); 
Console.Write("Group: {0} Teams: {1}", tg.Group, string.Join(", ", tg.Teams)); 

выходов :

Group: 2843360 Teams: TEAM1, TEAM2, TEAM3 
0

Если fullString не сгенерирован машиной, возможно, вам придется добавить ошибку или ловли, но это будет работать из коробки, и дать вам тест для работы против.

public string ParseoutGroup(string fullString) 
    { 
     var matches = Regex.Matches(fullString, @"group\s?=\s?'([^']+)'", RegexOptions.IgnoreCase); 
     return matches[0].Groups[1].Captures[0].Value; 
    } 

    public string[] ParseoutTeamNames(string fullString) 
    { 
     var teams = new List<string>(); 
     var matches = Regex.Matches(fullString, @"team\s?in\s?\((\s*'([^']+)',?\s*)+\)", RegexOptions.IgnoreCase); 
     foreach (var capture in matches[0].Groups[2].Captures) 
     { 
      teams.Add(capture.ToString()); 
     } 
     return teams.ToArray(); 
    } 

    [Test] 
    public void parser() 
    { 
     string test = "group = '2843360' and (team in ('team1', 'team2', 'team3'))"; 
     var group = ParseoutGroup(test); 
     Assert.AreEqual("2843360",group); 

     var teams = ParseoutTeamNames(test); 
     Assert.AreEqual(3, teams.Count()); 
     Assert.AreEqual("team1", teams[0]); 
     Assert.AreEqual("team2", teams[1]); 
     Assert.AreEqual("team3", teams[2]); 
    } 
0

Дополнение к @ BrunoLM, решение которого:

(Worth дополнительные строки, если вы будете иметь больше переменных, чтобы проверить позже):

Вы можете разбить строку на " и "ключевое слово" и функцию для проверки каждого предложения в отношении соответствующего оператора регулярного выражения и возврата желаемого значения.

(Непроверенный код, но он должен доставить эту идею.)

statments = statment.split('and') 
//So now: 
//statments[0] = "group = '2843360' " 
//statments[1] = "(team in ('TEAM1', 'TEAM2','TEAM3'))" 
foreach s in statments { 
    if (s.contains('group') group = RegexFunctionToExtract_GroupValue(s) ; 
    if (s.contains('team') teams = RegexFunctionToExtract_TeamValue(s) ; 
} 

Я считаю, что такой подход позволит обеспечить экологически чистые, простые в обслуживании код и небольшую оптимизацию.

Конечно, такой подход не ожидает предложения «ИЛИ». Тем не менее, это можно сделать с немного большей настройкой.

Смежные вопросы