Laden...

Regex Mapper: Groups eines Matches in Properties eines Objekts mappen

Erstellt von Sarc vor 12 Jahren Letzter Beitrag vor 12 Jahren 4.266 Views
S
Sarc Themenstarter:in
417 Beiträge seit 2008
vor 12 Jahren
Regex Mapper: Groups eines Matches in Properties eines Objekts mappen

Extension-Method zur Regex-Klasse, um anhand der Gruppennamen passende Objekte zu erzeugen

Hier erstmal das Snippet. Ein Beispiel folgt weiter unten.


public static class RegexExtensions
{
	public static IEnumerable<T> Matches<T>(this Regex regex, string input, ICustomConverter<T> converter = null) where T : class, new()
	{
		if (typeof(T) == typeof(object))
			return MatchesDynamic<T>(regex, input, converter);
		else
			return MatchesDefault<T>(regex, input, converter);
	}

	private static IEnumerable<T> MatchesDefault<T>(this Regex regex, string input, ICustomConverter<T> converter = null) where T : class, new()
	{
		var bindingFlags = BindingFlags.IgnoreCase | BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance;
		var t = typeof(T);

		foreach (Match m in regex.Matches(input).OfType<Match>().Where(f => f.Success))
		{
			T itm = new T();

			for (int i = 1; i < m.Groups.Count; ++i)
			{
				var groupName = regex.GroupNameFromNumber(i);

				if (converter == null || !converter.Convert(groupName, m.Groups[i], itm))
				{
					var pi = t.GetProperty(groupName, bindingFlags);

					if (pi != null)
						pi.SetValue(itm, Convert.ChangeType(m.Groups[i].Value, pi.PropertyType), null);
				}
			}

			yield return itm;
		}
	}

	private static IEnumerable<T> MatchesDynamic<T>(this Regex regex, string input, ICustomConverter<T> converter = null) where T : class, new()
	{
		var groupNames = regex.GetGroupNames();

		foreach (Match m in regex.Matches(input).OfType<Match>().Where(f => f.Success))
		{
			dynamic itm = new DynamicResult(m, groupNames);

			for (int i = 1; i < m.Groups.Count; ++i)
			{
				var groupName = regex.GroupNameFromNumber(i);

				if (converter != null)
					converter.Convert(groupName, m.Groups[i], itm);
			}

			yield return itm;
		}
	}

	public interface ICustomConverter<T>
	{
		// Liefert true, falls manuell konvertiert wurde, ansonsten false
		bool Convert(string groupName, Group group, T item);
	}

	private sealed class DynamicResult : DynamicObject
	{
		private Dictionary<string, string> values;

		public DynamicResult(Match match, string[] groupNames)
		{
			this.values = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);

			foreach (var grpName in groupNames)
				this.values.Add(grpName, match.Groups[grpName].Value);
		}

		public override bool TryGetMember(GetMemberBinder binder, out object result)
		{
			string grpValue = null;

			result = this.values.TryGetValue(binder.Name, out grpValue) ? grpValue : null;
			return result != null;
		}

		public override bool TrySetMember(SetMemberBinder binder, object value)
		{
			this.values[binder.Name] = value.ToString();
			return true;
		}
	}
}

Hat man beispielsweise Zeichenfolgen die die Grundlage für bestimmte Objekte darstellen, so kann man diese automatisch mappen lassen.
Das Beispiel zeigt ein Regex-Muster für Personendaten und die entsprechende Person-Klasse.
Die Namen der Regex-Gruppen müssen den Properties der Klasse entsprechen. Falls dies nicht möglich sein sollte oder man in das Mapping eingreifen möchte,
so kann ein ICustomConverter-Objekt übergeben werden.
Die Verwendung von "regexCustomConverter" zeigt dies beispielhaft.
Ausserdem gibt es noch eine dynamische Variante, indem man als generischen Parameter 'dynamic' übergibt.


static class Program
{
	static void Main(string[] args)
	{
		var regex = new Regex(@"(?<vorname>\w+)\s+(?<nachname>\w+)\s+\((?<age>\d+)\)");
		var regexCustomConverter = new Regex(@"(?<vorname>\w+)\s+(?<nachname>\w+)\s+\((?<age>\d+)\)\s+\((?<gender>[a-z])\)");

		// "Normale" Verwendung
		foreach (var p in regex.Matches<Person>("Max Mustermann (23)"))
		{
			Console.WriteLine(p.Vorname);
			Console.WriteLine(p.Nachname);
			Console.WriteLine(p.Age);
		}

		Console.WriteLine();

		// Verwendung eines CustomConverters
		foreach (var p in regexCustomConverter.Matches<Person>("Max Mustermann (23) (m)", new PersonConverter()))
		{
			Console.WriteLine(p.Vorname);
			Console.WriteLine(p.Nachname);
			Console.WriteLine(p.Age);
			Console.WriteLine(p.Gender);
		}

		Console.WriteLine();

		// Verwendung mittels dynamic
		foreach (var p in regex.Matches<dynamic>("Max Dynamic (23)"))
		{
			Console.WriteLine(p.Vorname);
			Console.WriteLine(p.Nachname);
			Console.WriteLine(p.Age);
		}

		Console.WriteLine();

		// Verwendung mittels dynamic (und Converter)
		foreach (var p in regexCustomConverter.Matches<dynamic>("Max Dynamic (23) (f)", new DynamicConverter()))
		{
			Console.WriteLine(p.Vorname);
			Console.WriteLine(p.Nachname);
			Console.WriteLine(p.Age);
			Console.WriteLine(p.Gender);
		}

		Console.ReadLine();
	}

	public class PersonConverter : RegexExtensions.ICustomConverter<Person>
	{
		public bool Convert(string groupName, Group group, Person item)
		{
			if (string.Equals("gender", groupName))
			{
				item.Gender = string.Equals(group.Value, "m") ? Gender.Male : Gender.Female;
				return true;
			}

			return false;
		}
	}

	public class DynamicConverter : RegexExtensions.ICustomConverter<object>
	{
		public bool Convert(string groupName, Group group, object item)
		{
			dynamic d = item;

			if (string.Equals("gender", groupName))
			{
				d.Gender = string.Equals(group.Value, "m") ? Gender.Male : Gender.Female;
				return true;
			}

			return false;
		}
	}

}

public class Person
{
	public string Vorname { get; set; }
	public string Nachname { get; set; }
	public int Age { get; set; }
	public Gender Gender { get; set; }
}

public enum Gender
{
	Male,
	Female
}

Viel Spass damit!

Schlagwörter: Regex, Mapper, Mapping

2.891 Beiträge seit 2004
vor 12 Jahren

Schick.
Du könntest T noch auf Referenztypen einschränken, da der Code nicht für Werttypen funktioniert (oder du passt den Code noch für Werttypen an 😃 ).

S
Sarc Themenstarter:in
417 Beiträge seit 2008
vor 12 Jahren

Du hast natürlich Recht. Ich habe es jetzt mal auf Referenztypen beschränkt.

Ausserdem habe ich noch eine dynamische Variante hinzugefügt.