1

I'm accepting an input string that I want to be a ternary statement that works on strings. So my method signature would look like this:

public string Parse(string value, string ternaryStatement)  

and there parameters would give these results:

Parse(null, "=?It's Null:It's Not Null") == "It's Null" // Empty string would 
Parse("", "=?It's Null:It's Not Null") == "It's Null"   // be same as null

This example is fairly simple, Split the string first by '?' then by ':'

But of course I need a method to handle escape characters, "\", "\?" and ":", where "\" is valid anywhere, "\?" would only be valid before the first unescaped "?" and ":" would only be valid after that same "?".

Parse(@"\?\", @"=\\\?\\?\:Match\::\:No Match\:") == ":Match:"
Parse(@"\?\", @"!=\\\?\\?\:No Match\::\:Match\:") == ":Match:"

But this is really complicated. I believe I can perform it using regular expressions, but that just creates another problem since this is well beyond my limited understanding of regular expressions. What's the best way to tackle this problem?

Edit 1

Some of the background: I'm storing a format for a URL in a database config table (It's actually Dynamics 365 for Customer Engagement, but that doesn't matter at this point). The format is stored as strings, and the parameters that are required are defined in code. So generally it looks like this:

Format: "https://something.com?Foo={0}&Bar={1}"

Description: "0 - Foo, 1 - Bar"

where the description is used both for the person that is formatting the url, and the developer that needs to know how to structure the format statement.

The problem I'm running into right now is that I have a url that requires at least one of two different parameters. If one of the values is null or empty, it will error if included in the url. So I need a way of saying, if Foo is null or Bar is null, don't include the name or &. Ideally I'd like to implement this like this:

"https://something.com?{0:=?:Foo={{0}}}&}{1:=?:Bar={{1}}}}" 

So if Foo is null and Bar is "Bar" the output would be

"https://something.com?Bar=Bar"

I could also see this being used if we need to switch between a 0/1 for a boolean to true/false without having to change code:

"https://something.com?{0:=0?false:true}" 
Daryl
  • 18,592
  • 9
  • 78
  • 145
  • Why do you want to do this? Give us some context as to why this is useful to you. – mjwills Jun 08 '17 at 12:04
  • @mjwills I'm trying to create a string formatter that handles nulls among other things... So `Format("{0:=?My Default Value:{{0}}}", value)` and I can't put that logic as a parameter to the format call since I need it to be defined by the string itself, and I don't have a method to include another Default string as a parameter. – Daryl Jun 08 '17 at 12:16
  • Can you share some more example inputs and outputs? I am still struggling to understand the underlying issue. My apologies. – mjwills Jun 08 '17 at 12:20
  • @mjwills does the update help? – Daryl Jun 08 '17 at 13:21
  • Given your update, perhaps it's better to look at the UriBuilder for this? https://stackoverflow.com/a/14517976/361842 – JohnLBevan Jun 08 '17 at 13:47
  • See my updated answer for an example. – JohnLBevan Jun 08 '17 at 14:13

2 Answers2

2

The two regexes should be:

Regex rx = new Regex(@"(?<=(?:^|[^\\])(?:\\\\)*)\?");
Regex rx2 = new Regex(@"(?<=(?:^|[^\\])(?:\\\\)*):");

Use them like:

var m = rx.Match(str);

if (m.Success)
{
    int ix = m.Index;
}

The main point of the two rx is that the searched string (\? or :) must be preceded by

(?<=(?:^|[^\\])(?:\\\\)*)

that is the beginning of the string ^ or not a \ ([^\\]) plus zero or an even number of \\ that is (?:\\\\)*.

A all-in-one regex is:

Regex rx = new Regex(@"^(?<operator>=|!=|<=|>=|<|>)(?<cmp>(?:(?:\\.)|[^?:])*)\?(?<true>(?:(?:\\.)|[^?:])*):(?<false>(?:(?:\\.)|[^?:])*)$");

if (m.Success)
{
    string op = m.Groups["operator"].Value;
    string cmp = m.Groups["cmp"].Value;
    string true1 = m.Groups["true"].Value;
    string false1 = m.Groups["false"].Value;
}

In op you'll get the comparison operator used, in cmp the comparand, in true1 and false1 the true and false strings. If !m.Success then the string isn't correctly formatted. Comprehending the regex is left as a simple exercise for the reader (unless you comprehend a regex, you shouldn't ever use it, because before or later you'll have to modify it/fix it/debug it)

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • You mean I can't use it, and put a link to this answer in the code so I can ping you in the future? ;) – Daryl Jun 08 '17 at 13:27
  • 1
    @Daryl In the end you can do whatever you want *if* you don't work with me, near me, for my contractor, near my contractor, for hospitals near where I live :-) – xanatos Jun 08 '17 at 13:29
  • 1
    `unless you comprehend a regex, you shouldn't ever use it` ... My refactor is going to be painful. – Pogrindis Apr 12 '18 at 11:51
1

Solution to returning different values based on input string

Why do you need to pass in a ternary statement / wouldn't this make more sense?

string Parse(string value, string returnIfNull, string returnIfNotNull) 
{
    return string.IsNullOrEmpty(value) ? returnIfNull: returnIfNotNull;
}

Console.WriteLine(Parse("", "treat as null", "not expected"));
Console.WriteLine(Parse("hello", "not expected", "this value's not null"));

Parsing a ternary string for values

However, if you really need to do this, you could use something like the below:

private static readonly Regex TernaryParserRegex = new Regex(
    @"^=\?(?<ifNull>(?:\\(\\\\)*:|[^:])*)(?<!\\):(?<ifNotNull>(?:\\(\\\\)*:|[^:])*)$"
    /* , RegexOptions.Compiled //include this line for performance if appropriate */
);      
private string[] ParseTernaryString (string ternaryStatement)
{
    var results = TernaryParserRegex.Match(ternaryStatement);
    if (results.Success) 
    {
        string[] returnVal = {
            results.Groups["ifNull"].Value
            , 
            results.Groups["ifNotNull"].Value
        };
        return returnVal;
    }
    else
    {
        throw new Exception("Invalid Ternary Statement"); //use an appropriate exception type here; or have the function return `{null,null}` / some other default as appropriate 
    }
}
public string Parse(string value, string ternaryStatement) 
{
    var returnValues = ParseTernaryString(ternaryStatement);
    return string.IsNullOrEmpty(value) ? returnValues[0]: returnValues[1];
}

//Example Usage:
Console.WriteLine(Parse("", "=?treat as null:not expected"));
Console.WriteLine(Parse("hello", "=?not expected:this value's not null"));

An explanation of the regex & additional examples are available here: https://regex101.com/r/YJ9qd3/1


Appending non-null/blank values to a URL's Query String

public void Main()
{
    var url = "https://example.com?something=keepMe&foo=FooWasHere&bar=swapMeQuick";
    var dict = new System.Collections.Generic.Dictionary<string, string>();
    dict.Add("foo", null);
    dict.Add("bar", "hello");
    dict.Add("boo", "new");
    Console.WriteLine(CreateUri(url, dict).ToString());
}

Uri CreateUri(string uri, IDictionary<string, string> parameters) 
{
    return CreateUri(new Uri(uri), parameters);
}
Uri CreateUri(Uri uri, IDictionary<string, string> parameters)
{
    var query = HttpUtility.ParseQueryString(uri.Query); //https://msdn.microsoft.com/en-us/library/ms150046(v=vs.110).aspx; though returns HttpValueCollection
    foreach (string key in parameters.Keys)
    {
        if (string.IsNullOrEmpty(parameters[key]))
        { //parameter is null or empty; if such a parameter already exists on our URL, remove it
            query.Remove(key); //if this parameter does not already exist, has no effect (but no exception is thrown)
        }
        else
        { //parameter has a value; add or update the query string with this value
            query.Set(key, parameters[key]);
        }
    }
    var builder = new UriBuilder(uri);
    builder.Query = query.ToString();
    return builder.Uri;
}
JohnLBevan
  • 22,735
  • 13
  • 96
  • 178
  • 1
    The table that stores these setting does not always store urls, so although I see this working, it doesn't accomplish what I need it to. Thanks though. – Daryl Jun 08 '17 at 14:31