0

I have a URL: Expand=User($select=Firstname,Lastname),Organisation,Contract($Expand=MyOrganisation($select=Name,Status),Organisation),List

I need to split this string in the below format:

  • User($select=Firstname,Lastname)

  • Organisation

  • Contract($Expand=MyOrganisation($select=Name,Status),Organisation)

  • List

How to achieve this functionality in C#?

Amal K
  • 4,359
  • 2
  • 22
  • 44

2 Answers2

2

More likely you have to use an ODataLib with in-built URI Parser

Uri requestUri = new Uri("Products?$select=ID&$expand=ProductDetail" +
                         "&$filter=Categories/any(d:d/ID%20gt%201)&$orderby=ID%20desc" +
                         "&$top=1&$count=true&$search=tom",
                         UriKind.Relative);
ODataUriParser parser = new ODataUriParser(model, serviceRoot, requestUri);
SelectExpandClause expand = parser.ParseSelectAndExpand(); // parse $select, $expand
FilterClause filter = parser.ParseFilter();                // parse $filter
OrderByClause orderby = parser.ParseOrderBy();             // parse $orderby
SearchClause search = parser.ParseSearch();                // parse $search
long? top = parser.ParseTop();                             // parse $top
long? skip = parser.ParseSkip();                           // parse $skip
bool? count = parser.ParseCount();                         // parse $count

Adding the RegExp option (the fixed version of what Amal has provided below)

    string url = "User($select=Firstname,Lastname),Organisation,Contract($Expand=MyOrganisation($select=Name,Status),Organisation),List";

    Regex rgx = new Regex(@"(.+?)(?:(\(.*?\)),|,)");

    foreach (var match in rgx.Matches($"{url},"))
    {
        Console.WriteLine(match.ToString()[..^1]);
    }
Boris Sokolov
  • 1,723
  • 4
  • 23
  • 38
  • we don't have a model we are using dynamic or Jobject. I was tried it's not working without a model. – Suresh Sankar Jun 11 '21 at 08:23
  • A manual parsing would be problematic as you don't have a specific delimiter (comma is used for multiple purposes). So if you know for sure that there is ")," statement, then "," and ")," afterwards - you can go with IndexOf() and substrigns – Boris Sokolov Jun 11 '21 at 08:45
  • 1
    That's a nice way of using indices and ranges. Note that they are only supported since C# 8.0 – Amal K Jun 11 '21 at 09:21
2

You not only need to split the string but also keep track of the parentheses while splitting. This is not possible with just plain old regex. See this post. However, the splitting can be achieved with some advanced RegEx; .NET fortunately supports balancing groups using which you can keep track of the parentheses. This answer was quite helpful in coming up with a solution. For readability, I have split the regex into multiple lines and used RegexOptions.IgnorePatternWhitespace:

string url = "User($select=Firstname,Lastname),Organisation,Contract($Expand=MyOrganisation($select=Name,Status),Organisation),List";
Regex rgx = new Regex(
               @"(.+?)
                (
                    (
                        \(
                            (?:
                                [^()]
                                |
                                (?'open'\()
                                |
                                (?'close-open'\))
                            )+
                            (?(open)(?!))
                        \)
                    )
                    ,
                    |
                    ,
                    |
                    \b$
                )", 
               RegexOptions.IgnorePatternWhitespace);

foreach(var match in rgx.Matches(url))
{
    Console.WriteLine($"{match.Groups[1]} {match.Groups[3]}"); 
}

The field will be available as match.Groups[1] and the parameters, if any will be available as match.Groups[3](this will be an empty string if there are no parameters). You can access match.Groups[0] to get the entire group.

Regex Breakdown

Regex Description
(.+?) Non-greedily match one or more characters
\( and \) Match an actual ( and )
[^()] Match any character that is not a ( or )
(?'open'\() Create a named group with the name "open" and match a ( character
(?'close-open\)) Create a group "close" and assign the interval between "open" and "close" to "close" and delete group "open"
(?(open)(?!)) Assert if the "open" group is not deleted
(?:[^()]|(?'open'\()|(?'close-open'\)))+ Create a non-capturing group and match one or more characters that match one of the expressions between |
Amal K
  • 4,359
  • 2
  • 22
  • 44