1

I have a HTML with the following content:

... some text ...
<a href="file.aspx?userId=123&section=2">link</a> ... some text ...
... some text ...
<a href="file.aspx?section=5&user=678">link</a> ... some text ...
... some text ...

I would like to parse that and get a match with named groups:

match 1

group["user"]=123

group["section"]=2

match 2

group["user"]=678

group["section"]=5

I can do it if parameters always go in order, first User and then Section, but I don't know how to do it if the order is different.

Thank you!

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278

9 Answers9

8

In my case I had to parse an Url because the utility HttpUtility.ParseQueryString is not available in WP7. So, I created a extension method like this:

public static class UriExtensions
{
    private static readonly Regex queryStringRegex;
    static UriExtensions()
    {
        queryStringRegex = new Regex(@"[\?&](?<name>[^&=]+)=(?<value>[^&=]+)");
    }

    public static IEnumerable<KeyValuePair<string, string>> ParseQueryString(this Uri uri)
    {
        if (uri == null)
            throw new ArgumentException("uri");

        var matches = queryStringRegex.Matches(uri.OriginalString);
        for (int i = 0; i < matches.Count; i++)
        {
            var match = matches[i];
            yield return new KeyValuePair<string, string>(match.Groups["name"].Value, match.Groups["value"].Value);
        }
    }
}

Then It's matter of using it, for example

        var uri = new Uri(HttpUtility.UrlDecode(@"file.aspx?userId=123&section=2"),UriKind.RelativeOrAbsolute);
        var parameters = uri.ParseQueryString().ToDictionary( kvp => kvp.Key, kvp => kvp.Value);
        var userId = parameters["userId"];
        var section = parameters["section"];

NOTE: I'm returning the IEnumerable instead of the dictionary directly just because I'm assuming that there might be duplicated parameter's name. If there are duplicated names, then the dictionary will throw an exception.

hmadrigal
  • 1,020
  • 11
  • 19
  • Good regex for querystring parsing: `[\?&](?[^&=]+)=(?[^&=]+)` or with a slight modification, just for querystrings: `[^&](?[^=]+)=(?[^&=]*)` – GlennG Dec 21 '16 at 14:58
5

Why use regex to split it out?

You could first extrct the query string. Split the result on & and then create a map by splitting the result from that on =

John Nilsson
  • 17,001
  • 8
  • 32
  • 42
1

You didn't specify what language you are working in, but this should do the trick in C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace RegexTest
{
    class Program
    {
        static void Main(string[] args)
        {
            string subjectString = @"... some text ...
                <a href=""file.aspx?userId=123&section=2"">link</a> ... some text ...
... some text ...
<a href=""file.aspx?section=5&user=678"">link</a> ... some text ...
... some text ...";
            Regex regexObj = 
               new Regex(@"<a href=""file.aspx\?(?:(?:userId=(?<user>.+?)&section=(?<section>.+?)"")|(?:section=(?<section>.+?)&user=(?<user>.+?)""))");
            Match matchResults = regexObj.Match(subjectString);
            while (matchResults.Success)
            {
                string user = matchResults.Groups["user"].Value;
                string section = matchResults.Groups["section"].Value;
                Console.WriteLine(string.Format("User = {0}, Section = {1}", user, section));
                matchResults = matchResults.NextMatch();
            }
            Console.ReadKey();
        }
    }
}
Mike Moore
  • 1,330
  • 1
  • 14
  • 20
0

Using regex to first find the key value pairs and then doing splits... doesn't seem right.

I'm interested in a complete regex solution.

Anyone?

0

Check this out

\<a\s+href\s*=\s*["'](?<baseUri>.+?)\?(?:(?<key>.+?)=(?<value>.+?)[&"'])*\s*\>

You can get pairs with something like Groups["key"].Captures[i] & Groups["value"].Captures[i]

0

Perhaps something like this (I am rusty on regex, and wasn't good at them in the first place anyway. Untested):

/href="[^?]*([?&](userId=(?<user>\d+))|section=(?<section>\d+))*"/

(By the way, the XHTML is malformed; & should be &amp; in the attributes.)

strager
  • 88,763
  • 26
  • 134
  • 176
0

Another approach is to put the capturing groups inside lookaheads:

Regex r = new Regex(@"<a href=""file\.aspx\?" +
                    @"(?=[^""<>]*?user=(?<user>\w+))" +
                    @"(?=[^""<>]*?section=(?<section>\w+))";

If there are only two parameters, there's no reason to prefer this way over the alternation-based approaches suggested by Mike and strager. But if you needed to match three parameters, the other regexes would grow to several times their current length, while this one would only need another lookahead like just like the two existing ones.

By the way, contrary to your response to Claus, it matters quite a bit which language you're working in. There's a huge variation in capabilities, syntax, and API from one language to the next.

Alan Moore
  • 73,866
  • 12
  • 100
  • 156
0

You did not say which regex flavor you are using. Since your sample URL links to an .aspx file, I'll assume .NET. In .NET, a single regex can have multiple named capturing groups with the same name, and .NET will treat them as if they were one group. Thus you can use the regex

userID=(?<user>\d+)&section=(?<section>\d+)|section=(?<section>\d+)&userID=(?<user>\d+)

This simple regex with alternation will be far more efficient than any tricks with lookaround. You can easily expand it if your requirements include matching the parameters only if they're in a link.

Jan Goyvaerts
  • 21,379
  • 7
  • 60
  • 72
0

a simple python implementation overcoming the ordering problem

In [2]: x = re.compile('(?:(userId|section)=(\d+))+')

In [3]: t = 'href="file.aspx?section=2&userId=123"'

In [4]: x.findall(t)
Out[4]: [('section', '2'), ('userId', '123')]

In [5]: t = 'href="file.aspx?userId=123&section=2"'

In [6]: x.findall(t)
Out[6]: [('userId', '123'), ('section', '2')]
altunyurt
  • 2,821
  • 3
  • 38
  • 53