Метод кодирует все символы меньше (

Я очищаю строку HTML, чтобы закодировать знак «меньше» (<) в объект HTML (&lt;), но оставив HTML-теги такими, какие они есть. Примером является преобразование "<div>Total < 500</div>" к "<div>Total &lt; 500</div>".

Есть ряд сообщений, посвященных этому, в том числе:
https://stackoverflow.com/questions/5464686/html-entity-encode-text-only-not-html-tag, https://stackoverflow.com/questions/2245066/encode-html-in-asp-net-c-sharp-but-leave-tags-intact, https://stackoverflow.com/questions/28301206/how-to-encode-special-characters-in-html-but-exclude-tags
Каждый пост указывает на использование Html Agility Pack и, в частности, HtmlEntity.Entitize(html) метод. Это не работает для меня, потому что на самом деле игнорируются знаки <и &, и, используя приведенный выше пример, вывод был таким же, как и ввод! Возможно, я не понимаю, как использовать этот метод, но код был просто таким:

public static string EntitizeHtml( this string html )
{
   return HtmlEntity.Entitize( html );
}

Я решил написать свой собственный метод, чтобы найти символы меньше чем и преобразовать их в эквивалент сущности HTML. Это работает, но кажется очень неуклюжим (цикл внутри цикла и множество манипуляций со строками). Я избегал Regex, чтобы он был быстрее и удобнее для чтения. Я полностью понимаю, что манипулирование строками HTML часто сопряжено с проблемами из-за различных версий HTML и искаженного HTML, но для моей цели этот метод решает мою проблему на основе дискретного набора тегов HTML. Следующий метод преобразует "<div>Total < 500</div>" к "<div>Total &lt; 500</div>" как и ожидалось.

Если кто-то сможет повысить эффективность этого метода, я был бы очень признателен.

        public static string EncodeLessThanEntities( this string html )
        {
            if( !html.Contains( "<" ) )
            {
                return html;
            }

            // get the full html length
            int length = html.Length;
            // set a limit on the tag string to compare to reduce the 
            // string size (i.e. the longest tags are <center> and <strong>)
            int tagLength = 6;
            // gather all the included html tags to check
            string s = "div|span|p|br|ol|ul|li|center|font|strong|em|sub|sup";
            string[] tags = s.Split( '|' ).ToArray( );
            // find all the indices of the less than entity or tag
            var indices = AllIndexesOf( html, "<" );
            // initiate a list of indices to be replaced
            var replaceable = new List<int>( );

            // loop through the indices
            foreach( var index in indices )
            {
                // store the potential tag (up to the tag length)
                if( length - ( index + 1 ) < tagLength ) tagLength = length - ( index + 1 );
                string possibleTag = html.Substring( index + 1, tagLength );
                // automatically ignore any closing tags
                if( possibleTag.Substring( 0, 1 ) == "/" )
                {
                    continue;
                }

                bool match = false;

                // loop through each html tag to find a match
                foreach( var tag in tags )
                {
                    if( possibleTag.StartsWith( tag ) )
                    {
                        match = true;
                        break;
                    }
                }

                if( !match )
                {
                    // if there is no match to a html tag, store the index
                    replaceable.Add( index );
                }
            }

            if( replaceable?.Any( ) ?? false )
            {
                // loop through each index and replace the '<' with '&lt;'
                foreach( var index in Enumerable.Reverse( replaceable ) )
                {
                    html = html.ReplaceAt( index, 1, "&lt;" );
                }
            }

            return html;
        }


        public static List<int> AllIndexesOf( this string input, string value )
        {
            List<int> indexes = new List<int>( );

            if( string.IsNullOrEmpty( value ) )
            {
                return indexes;
            }

            for( int index = 0; ; index += value.Length )
            {
                index = input.IndexOf( value, index );
                if( index == -1 )
                {
                    return indexes;
                }
                indexes.Add( index );
            }
        }


        public static string ReplaceAt( this string str, int index, int length, string replace )
        {
            return str.Remove( index, Math.Min( length, str.Length - index ) ).Insert( index, replace );
        }

Я надеюсь, что есть способ сделать описанный выше метод более эффективным. Или, может быть, это так хорошо, как есть? Я знаю, что есть много очень умных парней с гораздо большим опытом, и я не включаю себя в эту группу!

1 ответ
1

  • Каждый раз, когда ты звонишь Substring, Remove, Replace или любой другой метод, изменяющий string, вы создаете новый string экземпляр с новым выделением памяти. Потому что string неизменен. Все string операции (относительные к числовым) медленные / дорогие. Но ничего страшного, если иметь в виду этот вопрос и соглашаться с ним.

  • html.Contains, AllIndexesOf, foreach(var index in indices) — 2,5 сканирования одной и той же строки.

  • possibleTag.Substring(0, 1) == "/", почему нет possibleTag[0] == "https://codereview.stackexchange.com/"? Быстрее бы.

  • if (replaceable?.Any() ?? false) 1) replaceable не может быть null, потом if (replaceable.Any()) почти нормально 2) но это просто проверить List содержит любые элементы, то if (replaceable.Length > 0) это лучше. 3) но foreach не будет обрабатывать коллекцию, если она пуста, даже Enumerable.Reverse вызов подходит для пустой коллекции. Таким образом вы можете стереть if заявление полностью.

  • Самый быстрый способ построить строку из данных в .NET Framework — это StringBuilder. (.NET Core и более новые версии .NET имеют Span-основанный метод string.Create что в некоторых случаях быстрее)

И напоследок мой вариант реализации

// required array can be created once per application start
private static readonly string[] tags = "div span p br ol ul li center font strong em sub sup".Split();

public static string EncodeLessThanEntities(this string html)
{
    if (html.Length < 8) // the shortest valid html is <p></p>: 7 chars but there's no room for other chars
        return html;
    StringBuilder sb = new StringBuilder(html.Length); // spawn StringBuilder with initial capacity, this will reduce amount of memory allocations
    int i;
    for (i = 0; i < html.Length - 2; i++)
    {
        if (html[i] == '<' && !tags.Any(tag => html.StartsWithAt(i + (html[i + 1] == "https://codereview.stackexchange.com/" ? 2 : 1), tag)))
            sb.Append("&lt;");
        else
            sb.Append(html[i]);
    }
    // 'i' has value 'html.Length - 2' here, append two last chars without changes
    return sb.Append(html[i]).Append(html[i + 1]).ToString();
}

// same as `String.StartsWith` but accepts a start index
public static bool StartsWithAt(this string text, int startIndex, string value)
{
    if (text.Length - startIndex < value.Length)
        return false;

    for (int i = 0; i < value.Length; i++)
    {
        if (text[startIndex + i] != value[i])
            return false;
    }
    return true;
}

Я не тестировал это много, но вы можете.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *