Сокращение конечного вывода HTML с помощью регулярных выражений с CodeIgniter - PullRequest
16 голосов
/ 15 марта 2011

На страницах 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 очень хорошо проанализировал это регулярное выражение.Я закончил тем, что использовал его решение.Приветствия к Ridgerunner.

Ответы [ 2 ]

48 голосов
/ 16 марта 2011

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

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;
}

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

  1. Существует ненужная группа захвата, которую можно удалить.
  2. Хотя ОП не сказал об этом, элемент <SCRIPT> должен быть добавлен в черный список <PRE> и <TEXTAREA>.
  3. Добавление модификатора "Study" 'S' PCRE ускоряет это регулярное выражение примерно на 20%.
  4. В прогнозной группе есть альтернативная группа, которая готова применить эффективную конструкцию Фридла " unrolling-the-loop ".
  5. На более серьезном замечании, та же самая группа альтернатив: (то есть (?:[^<]++|<(?!/?(?:textarea|pre)\b))*+) подвержена чрезмерной рекурсии PCRE для больших целевых строк, что может привести к переполнению стека, в результате чего исполняемый файл Apache / PHP молча ошибка сегмента и сбой без предупреждения. (Win32-сборка Apache httpd.exe особенно восприимчива к этому, потому что она имеет стек только 256 КБ по сравнению с исполняемыми файлами * nix, которые обычно создаются со стеком 8 МБ или более.) Филипп Хейзел (автор механизма регулярных выражений PCRE, используемого в PHP) обсуждает эту проблему в документации: PCRE ОБСУЖДЕНИЕ ИСПОЛЬЗОВАНИЯ STACK . Хотя Алан правильно применил то же исправление, что и Филип, который показывает в этом документе (применяя притяжательный плюс к первому варианту), все равно будет много рекурсии, если файл HTML большой и содержит много не внесенных в черный список тегов. например На моем Win32-боксе (с исполняемым файлом со стеком 256 КБ) сценарий взрывается с тестовым файлом размером всего 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, поскольку я помогал сообществу Drupal, пока они боролись с этой проблемой. См .: Оптимизация опции CSS приводит к сбою php cgi в функции pcre "match" . Мы также испытали это с анализатором BBCode в программном проекте форума FluxBB.

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

1 голос
/ 11 августа 2016

Я реализовал ответ @ridgerunner в двух проектах и ​​в итоге столкнулся с некоторыми серьезными замедлениями (время запроса 10-30 секунд) при подготовке одного из проектов.Я обнаружил, что должен был установить оба значения pcre.recursion_limit и pcre.backtrack_limit достаточно низкими, чтобы он работал даже, но даже тогда он сдавался примерно через 2 секунды обработки и возвращал 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...