Я получаю текст от API, в котором есть немного HTML, а именно <span>s и <a>s. Я буду использовать эти части для заполнения свойства FormattedText Xamarin.Forms Label.
У меня есть следующий код, который отлично работает, но кажется довольно неэффективным, с внешним регулярным выражением и еще тремя на цикл.
Мне было интересно, есть ли какое-нибудь более продвинутое регулярное выражение, которое я мог бы использовать, чтобы лучше разбить это, чтобы получить атрибуты class и href, которые мне нужны.
Учитывая этот ввод:
one<span class=\"a-class\">two</span>three<a href=\"#a-link\">four</a>five
Правильно дает:
one ->
two -> a-class
three ->
four -> #a-link
five ->
Код:
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
public class Program
{
public class StringPart
{
public string Text { get; set; }
public string Class { get; set; }
public string Link { get; set; }
public bool IsClass => !string.IsNullOrEmpty(Class);
public bool IsLink => !string.IsNullOrEmpty(Link);
public string Info
=> IsClass ? Class : IsLink ? Link : string.Empty;
}
public static void Main()
{
var text = "one<span class=\"a-class\">two</span>three<a href=\"#a-link\">four</a>five";
var parts = new List<StringPart>();
var idx = 0;
// Matches '<span class=\"a-class\">two</span>'
// & '<a href=\"#a-link\">four</a>'
foreach (Match match in new Regex($"<(.*?)>(.*?)</(.*?)>").Matches(text))
{
// preceeds match
parts.Add(new StringPart { Text = text.Substring(idx, match.Index - idx) });
// a match, has either span or a props
// 3 more regex, though
parts.Add(new StringPart
{
Text = Regex.Replace(match.Value, "<.*?>", string.Empty),
Link = Regex.Match(match.Value, "(?<=href=\\\")[\\S]+(?=\\\")").Value,
Class = Regex.Match(match.Value, "(?<=class=\\\")[\\S]+(?=\\\")").Value
});
// move idx for next preceeding part
idx = match.Index + match.Length;
}
// remaining after last match
parts.Add(new StringPart { Text = text.Substring(idx) });
// dump
foreach (var p in parts)
Console.WriteLine($"{p.Text} -> {p.Info}");
}
}
Судя по выходным данным API, это будут исключительно a с href и span с class. Никаких других атрибутов или тегов, которые я наблюдал. Но их могло быть любое количество, от нуля до горстки. Там несколько <br>, но я их уже вырезал для \n.





Следующий код выдаст желаемый результат с одним регулярным выражением, хотя регулярное выражение немного запутано:
public static void Main()
{
var testString = "one<span class=\"a-class\">two</span>three<a href=\"#a-link\">four</a>five";
var matches = new Regex(@"^(?<Text>.+?)<|span class = ""(?<Class>.*?)"">(?<Text>.+?)</span|a href = ""(?<Link>.*?)"">(?<Text>.+?)</a|>(?<Text>.+?)<|>(?<Text>.+?)$").Matches(testString);
var parts = from m in matches.Cast<Match>()
select new StringPart
{
Text = m.Groups["Text"].Value,
Class = m.Groups["Class"].Value,
Link = m.Groups["Link"].Value
};
// dump
foreach (var p in parts)
Console.WriteLine($"{p.Text} -> {p.Info}");
}
Давайте разберем регулярное выражение. Вот полное регулярное выражение без экранированных кавычек (мне пришлось избегать двойных кавычек, когда я копировал из моего тестера регулярных выражений в дословную строку C#):
^(?<Text>.*?)<|span class = "(?<Class>.*?)">(?<Text>.*?)</span|a href = "(?<Link>.*?)">(?<Text>.*?)</a|>(?<Text>.+?)<|>(?<Text>.+?)$
Выражение состоит из пяти частей, разделенных |. Каждая часть содержит одну или несколько именованных групп, которые собирают данные, которые нам нужны для этой части.
Именованная группа имеет следующий формат: (?<Name>...)
Вот пять частей:
^(?<Text>.+?)<: соответствует начальному тексту до первого < включительно.
span class = "(?<Class>.*?)">(?<Text>.+?)</span: соответствует элементу <span> и фиксирует текст и класс
a href = "(?<Link>.*?)">(?<Text>.+?)</a: соответствует элементу <a> и захватывает текст и ссылку
>(?<Text>.+?)<: сопоставляет текст с двумя элементами HTML, включая > и <.
>(?<Text>.+?)$: соответствует конечному тексту, включая последний >
Примечание 1. Это вернет ноль совпадений, если в вашей строке нет HTML-элементов (например, «один»). Возможно, лучше всего просто обработать этот особый случай отдельно.
Примечание 2: Предполагается, что исходная строка не содержит \, а они просто помещены туда, чтобы избежать двойных кавычек в примере кода C#. Если в строке будут присутствовать символы \, необходимо настроить регулярное выражение для поиска \ во второй и третьей частях выше.
(Вот регулярное выражение в тестере, которое я придумал: https://regex101.com/r/9C5dmy/2/ - помимо экранирования двойных кавычек при копировании в код C#, я смог переименовать все имена групп «Текст *» в просто «Текст» - regex101.com не позволяет дублировать имена групп, в отличие от C#, а наличие всех групп «Текст *», названных просто «Текст», упростило логику.)
ОБНОВЛЕНО: Переключены группы «Текст» с (?<Text>.*?) на (?<Text>.+?), чтобы гарантировать один или несколько символов, чтобы избежать совпадений с пустой строкой.
Насколько эта строка отличается в html-классах и т. д.