Оптимизация соответствия легкой HTML-части

Я получаю текст от 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}");        
    }
}

Насколько эта строка отличается в html-классах и т. д.

TheGeneral 29.08.2018 02:45

Судя по выходным данным API, это будут исключительно a с href и span с class. Никаких других атрибутов или тегов, которые я наблюдал. Но их могло быть любое количество, от нуля до горстки. Там несколько <br>, но я их уже вырезал для \n.

Josh 29.08.2018 02:46
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
47
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Следующий код выдаст желаемый результат с одним регулярным выражением, хотя регулярное выражение немного запутано:

    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>...)

Вот пять частей:

  1. ^(?<Text>.+?)<: соответствует начальному тексту до первого < включительно.

  2. span class = "(?<Class>.*?)">(?<Text>.+?)</span: соответствует элементу <span> и фиксирует текст и класс

  3. a href = "(?<Link>.*?)">(?<Text>.+?)</a: соответствует элементу <a> и захватывает текст и ссылку

  4. >(?<Text>.+?)<: сопоставляет текст с двумя элементами HTML, включая > и <.

  5. >(?<Text>.+?)$: соответствует конечному тексту, включая последний >

Примечание 1. Это вернет ноль совпадений, если в вашей строке нет HTML-элементов (например, «один»). Возможно, лучше всего просто обработать этот особый случай отдельно.

Примечание 2: Предполагается, что исходная строка не содержит \, а они просто помещены туда, чтобы избежать двойных кавычек в примере кода C#. Если в строке будут присутствовать символы \, необходимо настроить регулярное выражение для поиска \ во второй и третьей частях выше.

(Вот регулярное выражение в тестере, которое я придумал: https://regex101.com/r/9C5dmy/2/ - помимо экранирования двойных кавычек при копировании в код C#, я смог переименовать все имена групп «Текст *» в просто «Текст» - regex101.com не позволяет дублировать имена групп, в отличие от C#, а наличие всех групп «Текст *», названных просто «Текст», упростило логику.)

ОБНОВЛЕНО: Переключены группы «Текст» с (?<Text>.*?) на (?<Text>.+?), чтобы гарантировать один или несколько символов, чтобы избежать совпадений с пустой строкой.

Другие вопросы по теме