Минимизация окончательного вывода HTML с использованием регулярных выражений с помощью CodeIgniter

Страницы Google предлагают вам минимизировать HTML, т.е. удалить все ненужные пробелы. CodeIgniter имеет функцию выхода giziping, или это можно сделать с помощью .htaccess. Но все же я также хотел бы удалить ненужные пробелы из окончательного вывода HTML.

Я немного поиграл с этим фрагментом кода, и это похоже на работу. Это действительно приводит к тому, что HTML не содержит лишних пробелов и удаляет другое форматирование табуляции.

class Welcome extends CI_Controller 
{
    function _output()
    {
        echo preg_replace('!\s+!', ' ', $output);
    }

    function index(){
    ...
    }
}

Проблема в том, что могут быть такие теги, как <pre>, <textarea> и т.д., которые могут иметь пробелы в них, и регулярное выражение должно их удалить. Итак, как удалить лишнее пространство из окончательного HTML, не делая пробелы или форматирование для этих определенных тегов, используя регулярное выражение?

Благодаря @Alan Moore получил ответ, это сработало для меня

echo preg_replace('#(?ix)(?>[^\S ]\s*|\s{2,})(?=(?:(?:[^<]++|<(?!/?(?:textarea|pre)\b))*+)(?:<(?>textarea|pre)\b|\z))#', ' ', $output);

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

Ответы

Ответ 1

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

function process_data_alan($text) // 
{
    $re = '%# Collapse ws everywhere but in blacklisted elements.
        (?>             # Match all whitespans other than single space.
          [^\S ]\s*     # Either one [\t\r\n\f\v] and zero or more ws,
        | \s{2,}        # or two or more consecutive-any-whitespace.
        ) # Note: The remaining regex consumes no text at all...
        (?=             # Ensure we are not in a blacklist tag.
          (?:           # Begin (unnecessary) group.
            (?:         # Zero or more of...
              [^<]++    # Either one or more non-"<"
            | <         # or a < starting a non-blacklist tag.
              (?!/?(?:textarea|pre)\b)
            )*+         # (This could be "unroll-the-loop"ified.)
          )             # End (unnecessary) group.
          (?:           # Begin alternation group.
            <           # Either a blacklist start tag.
            (?>textarea|pre)\b
          | \z          # or end of file.
          )             # End alternation group.
        )  # If we made it here, we are not in a blacklist tag.
        %ix';
    $text = preg_replace($re, " ", $text);
    return $text;
}

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

  • Существует ненужная группа захвата, которую можно удалить.
  • Хотя ОП не сказал этого, элемент <SCRIPT> должен быть добавлен в черный список <PRE> и <TEXTAREA>.
  • Добавление модификатора "исследования" 'S' PCRE "ускоряет" это регулярное выражение примерно на 20%.
  • В lookahead есть группа чередования, которая созрела для применения эффективности эффективности Friedl unrolling-the-loop.
  • В более серьезной заметке эта же группа чередования: (т.е. (?:[^<]++|<(?!/?(?:textarea|pre)\b))*+) подвержена чрезмерной рекурсии PCRE на больших целевых строках, что может привести к переполнению стека, в результате чего исполняемый файл Apache/PHP будет молча выполнять seg-fault и авария без предупреждения. (Узел Win32 Apache httpd.exe особенно восприимчив к этому, потому что он имеет всего 256 КБ стека по сравнению с исполняемыми файлами * nix, которые обычно создаются с использованием стека 8 МБ и более.) Филипп Хейзел (автор механизма регулярного выражения PCRE, используемого в PHP) обсуждает эту проблему в документации: PCR ОБСУЖДЕНИЕ ИСПОЛЬЗОВАНИЯ STACK. Хотя Алан правильно применил ту же ошибку, что и Филипп в этом документе (применяя притяжательный плюс к первому альтернативу), все еще будет много рекурсии, если HTML файл большой и имеет много нечерных тегов. например В моем ящике Win32 (с исполняемым файлом, имеющим стек 256 КБ), script взрывается тестовым файлом всего 60 КБ. Также обратите внимание, что PHP, к сожалению, не соответствует рекомендациям и устанавливает ограничение по рекурсии по умолчанию слишком высоко на 100000. (Согласно документам PCRE, это должно быть установлено равным размеру стека, разделенному на 500).

Вот улучшенная версия, которая быстрее оригинала, обрабатывает больший ввод и изящно выходит из строя с сообщением, если входная строка слишком велика для обработки:

// Set PCRE recursion limit to sane value = STACKSIZE / 500
// ini_set("pcre.recursion_limit", "524"); // 256KB stack. Win32 Apache
ini_set("pcre.recursion_limit", "16777");  // 8MB stack. *nix
function process_data_jmr1($text) // 
{
    $re = '%# Collapse whitespace everywhere but in blacklisted elements.
        (?>             # Match all whitespans other than single space.
          [^\S ]\s*     # Either one [\t\r\n\f\v] and zero or more ws,
        | \s{2,}        # or two or more consecutive-any-whitespace.
        ) # Note: The remaining regex consumes no text at all...
        (?=             # Ensure we are not in a blacklist tag.
          [^<]*+        # Either zero or more non-"<" {normal*}
          (?:           # Begin {(special normal*)*} construct
            <           # or a < starting a non-blacklist tag.
            (?!/?(?:textarea|pre|script)\b)
            [^<]*+      # more non-"<" {normal*}
          )*+           # Finish "unrolling-the-loop"
          (?:           # Begin alternation group.
            <           # Either a blacklist start tag.
            (?>textarea|pre|script)\b
          | \z          # or end of file.
          )             # End alternation group.
        )  # If we made it here, we are not in a blacklist tag.
        %Six';
    $text = preg_replace($re, " ", $text);
    if ($text === null) exit("PCRE Error! File too big.\n");
    return $text;
}

p.s. Я знаком с этой проблемой PHP/Apache seg-fault, так как я был вовлечен в помощь сообществу Drupal, когда они боролись с этой проблемой. Смотрите: Оптимизация параметра CSS заставляет php cgi выполнить segfault в функции pcre "match" . Мы также испытали это с помощью парсера BBCode в проекте программного обеспечения форума FluxBB.

Надеюсь, что это поможет.

Ответ 2

Я реализовал ответ от @ridgerunner в двух проектах и ​​столкнулся с некоторыми серьезными замедлениями (время запросов 10-30 секунд) для организации одного из проектов. Я выяснил, что мне пришлось установить как pcre.recursion_limit, так и pcre.backtrack_limit достаточно низко, чтобы он даже работал, но даже тогда он откажется после двух секунд обработки и вернет false.

С тех пор я заменил его на это решение (с упрощенным для понимания регулярным выражением), которое вдохновлено функцией outputfilter.trimwhitespace от Smarty 2. Она не выполняет обратную трассировку или рекурсию и не работает каждый раз (вместо этого катастрофически проваливается один раз в голубой луне):

function filterHtml($input) {
    // Remove HTML comments, but not SSI
    $input = preg_replace('/<!--[^#](.*?)-->/s', '', $input);

    // The content inside these tags will be spared:
    $doNotCompressTags = ['script', 'pre', 'textarea'];
    $matches = [];

    foreach ($doNotCompressTags as $tag) {
        $regex = "!<{$tag}[^>]*?>.*?</{$tag}>!is";

        // It is assumed that this placeholder could not appear organically in your
        // output. If it can, you may have an XSS problem.
        $placeholder = "@@<'-placeholder-$tag'>@@";

        // Replace all the tags (including their content) with a placeholder, and keep their contents for later.
        $input = preg_replace_callback(
            $regex,
            function ($match) use ($tag, &$matches, $placeholder) {
                $matches[$tag][] = $match[0];
                return $placeholder;
            },
            $input
        );
    }

    // Remove whitespace (spaces, newlines and tabs)
    $input = trim(preg_replace('/[ \n\t]+/m', ' ', $input));

    // Iterate the blocks we replaced with placeholders beforehand, and replace the placeholders
    // with the original content.
    foreach ($matches as $tag => $blocks) {
        $placeholder = "@@<'-placeholder-$tag'>@@";
        $placeholderLength = strlen($placeholder);
        $position = 0;

        foreach ($blocks as $block) {
            $position = strpos($input, $placeholder, $position);
            if ($position === false) {
                throw new \RuntimeException("Found too many placeholders of type $tag in input string");
            }
            $input = substr_replace($input, $block, $position, $placeholderLength);
        }
    }

    return $input;
}