Удалить все пустые теги HTML?

Я представляю себе функцию, которая, я думаю, будет использовать Regex, и она будет рекурсивной для таких случаев, как <p><strong></strong></p> удалить все пустые теги HTML внутри строки. Это должно было бы учитывать пробелы, если это возможно. Не было бы сумасшедших случаев, когда символ <использовался в значении атрибута.

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

Вот метод, который я имею до сих пор:

Public Shared Function stripEmptyHtmlTags(ByVal html As String) As String
    Dim newHtml As String = Regex.Replace(html, "/(<.+?>\s*</.+?>)/Usi", "")

    If html <> newHtml Then
        newHtml = stripEmptyHtmlTags(newHtml)
    End If

    Return newHtml
End Function

Однако мой текущий Regex в формате PHP, и он, похоже, не работает. Я не знаком с синтаксисом регулярных выражений.NET.

Всем тем, кто говорит, не используйте регулярные выражения: мне любопытно, каким будет шаблон, независимо от этого. Конечно, есть шаблон, который может сопоставить все открывающие / закрывающие начальные теги с любым количеством пробелов (или их нет) между тегами? Я видел регулярное выражение, которое сопоставляет теги HTML с любым количеством атрибутов, один пустой тег (например, просто <p></p>) так далее.

До сих пор я пробовал следующие шаблоны регулярных выражений в вышеописанном методе безрезультатно (например, у меня есть текстовая строка с пустыми тегами абзацев, которые даже не были удалены).

Regex.Replace(html, "/(<.+?>\s*</.+?>)/Usi", "")

Regex.Replace(html, "(<.+?>\s*</.+?>)", "")

Regex.Replace(html, "%<(\w+)\b[^>]*>\s*</\1\s*>%", "")

Regex.Replace(html, "<\w+\s*>\s*</\1\s*>", "")

4 ответа

Решение

Во-первых, обратите внимание, что пустые элементы HTML по определению не являются вложенными.

Обновление: нижеприведенное решение рекурсивно применяет регулярное выражение пустого элемента для удаления структур "nested-empty-element", таких как: <p><strong></strong></p> (с учетом оговорок, изложенных ниже).

Простая версия:

Это работает довольно хорошо (см. Предостережения ниже) для HTML, не имеющего атрибутов начального тега, содержащего <> забавные вещи, в виде (непроверенного) фрагмента VB.NET:

Dim RegexObj As New Regex("<(\w+)\b[^>]*>\s*</\1\s*>")
Do While RegexObj.IsMatch(html)
    html = RegexObj.Replace(html, "")
Loop

Улучшенная версия

<(\w+)\b(?:\s+[\w\-.:]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[\w\-.:]+))?)*\s*/?>\s*</\1\s*>

Вот расширенная версия без комментариев в VB.NET (не проверена):

Dim RegexObj As New Regex("<(\w+)\b(?:\s+[\w\-.:]+(?:\s*=\s*(?:""[^""]*""|'[^']*'|[\w\-.:]+))?)*\s*/?>\s*</\1\s*>")
Do While RegexObj.IsMatch(html)
    html = RegexObj.Replace(html, "")
Loop

Это более сложное регулярное выражение правильно соответствует действительному пустому элементу HTML 4.01, даже если в его значениях атрибута есть угловые скобки (еще раз, с соблюдением предостережений ниже). Другими словами, это регулярное выражение правильно обрабатывает все значения атрибута начального тега, которые заключены в кавычки (которые могут иметь <>), без кавычек (что не может) и пусто. Вот полностью прокомментированная (и протестированная) версия PHP:

function strip_empty_tags($text) {
    // Match empty elements (attribute values may have angle brackets).
    $re = '%
        # Regex to match an empty HTML 4.01 Transitional element.
        <                    # Opening tag opening "<" delimiter.
        (\w+)\b              # $1 Tag name.
        (?:                  # Non-capture group for optional attribute(s).
          \s+                # Attributes must be separated by whitespace.
          [\w\-.:]+          # Attribute name is required for attr=value pair.
          (?:                # Non-capture group for optional attribute value.
            \s*=\s*          # Name and value separated by "=" and optional ws.
            (?:              # Non-capture group for attrib value alternatives.
              "[^"]*"        # Double quoted string.
            | \'[^\']*\'     # Single quoted string.
            | [\w\-.:]+      # Non-quoted attrib value can be A-Z0-9-._:
            )                # End of attribute value alternatives.
          )?                 # Attribute value is optional.
        )*                   # Allow zero or more attribute=value pairs
        \s*                  # Whitespace is allowed before closing delimiter.
        >                    # Opening tag closing ">" delimiter.
        \s*                  # Content is zero or more whitespace.
        </\1\s*>             # Element closing tag.
        %x';
    while (preg_match($re, $text)) {
        // Recursively remove innermost empty elements.
        $text = preg_replace($re, '', $text);
    }
}

Предостережения: эта функция не анализирует HTML. Он просто сопоставляет и удаляет любую последовательность текстового шаблона, соответствующую действительному пустому элементу HTML 4.01 (который по определению не является вложенным). Обратите внимание, что это также ошибочно сопоставляет и удаляет тот же текстовый шаблон, который может встречаться вне обычной разметки HTML, например, внутри тегов SCRIPT и STYLE, комментариев HTML и атрибутов других начальных тегов. Это регулярное выражение не работает с короткими тегами. Любой поклонник bobenc, желающий дать этому ответу автоматический нисходящий голос, покажите мне один действительный пустой элемент HTML 4.01, которому это регулярное выражение не соответствует. Это регулярное выражение следует спецификации W3C и действительно работает.

Обновление: это решение для регулярных выражений также не работает (и ошибочно удалит допустимую разметку), если вы делаете что-то невероятно маловероятное (но совершенно правильное), как это:

<div att="<p att='">stuff</div><div att="'></p>'">stuff</div>

Резюме:

Если подумать, просто используйте анализатор HTML!

Проблема, с которой вы сталкиваетесь, заключается в произвольных уровнях вложенности, которые не могут быть сопоставлены со стандартным регулярным выражением. Я полагаю, вы можете применять одну и ту же замену регулярных выражений снова и снова, пока ничего не останется. Но есть и лучшие решения, такие как выделенная библиотека для разбора HTML.

Вы не можете сделать это с помощью регулярного выражения. Вы могли бы вероятно использовать анализатор xml, предполагая, что HTML хорошо сформирован.

Почему рекурсивный, хотя, вы могли бы просто запустить

 <(\w+)\s*>\s*</\1\s*>

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

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