Читаемая разбивка текста на страницы

Задача описанная в заголовке иногда возникает в разработке сайтов. Суть в следующем: есть очень большой текст (мегабайты), который хотелось бы не выдавать в одну страницу, а разбить на некоторое количество кусков примерно одинакового размера. Объём и структура текста заранее неизвестны. В качестве входных параметров выступают сам текст и номер страницы, которую надо показать. И важное замечание: речь пойдёт о просто текстах без какой-либо HTML-разметки. Тех самых, что живут в файлах с расширениями .txt и MIME-типом text/plain.

Первый вариант кода, что приходит в голову выглядит так:

 $page = substr($text, $number*$length, $length);

Здесь $text – это исходный текст, $number – номер требуемой страницы (нумерация начинается с нуля) и $length – размер самой страницы в байтах.

Вроде бы ничего сложного, всё тривиально, но свои подводные камни тут имеются. Функция <span style="color: #990000;">substr</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> – она дура, режет там, где вычислено. И поэтому будет часто попадать на середину слова, предложения или абзаца. Значит нарезка на абсолютно одинаковые куски будет приводить к затруднениям в читаемости текста в начале и в конце страниц.

Критерием читаемости может служить совпадение окончания страницы с окончанием очередного абзаца. Правда, этот абзац может быть весьма длинным. Тут всё зависит от писателя, чьему перу принадлежит искомый текст. Поэтому отыскивая конец абзаца, не надо забредать дальше, чем скажем 10-20% от принятого нами размера страницы. Эта величина будет служить еще одним критерием корректной разбивки на страницы.

Предположим, что в пределах отклонения в 10% конец абзаца не найден. Тогда надо разбить страницу в конце предложения. Если же и конец предложения в пределах установленного отклонения не найден, то мы будем искать хотя бы конец слова. Если же и концов слов не сыщешь, то тут придётся резать текст по-живому, в том месте, где вычислено.

Итак, алгоритм этой интеллектуальной нарезки текста определён, осталось понять, что у нас будет определять места, где кончаются абзацы, предложения и слова. В старых текстах, где на 80-м символе обязательно шёл перевод строки, для разметки абзацев использовали либо два перевода строки, либо перевод строки и пару пробелов для отступа нового абзаца. Текстовых файлов с таким форматированием до сих пор встречается очень много. В современных текстах каждый абзац представляет из себя одну строку завершающуюся её переводом. В русском языке (и в других европейских) предложения заканчиваются на точку, восклицательный или вопросительный знак с последующим пробелом. Ну а слова отделяются друг от друга просто пробелами.

Теперь пишем код:

// Параметры функции:
// $text - исходный текст;
// $page - номер страницы (первая - 0);
// $length - длина страницы в байтах;
// $deviation - максимальное отклонение в байтах
//   от расчетного положения конца страницы;
function page_text ($text, $page = 0, $length = 10000, $deviation = 1000) {
// Вырезаем часть страницы по требуемому номеру и её длине.
  $res = substr($text, $page*$length, $length);
// Находим общее число страниц в тексте.
  $count = ceil ((real)strlen($text)/(real)$length);
// Задаём массив строк, по которым определяется
// читаемая разбивка текста от абзацев до слов.
  $delimiters = array("\n\n", "\n ", "\n", ". ", "! ", "? ", " ");
// Если страница не первая, то на предыдущей могла быть найдена новая метка
// для разбивки на страницы, поэтому эту часть текста требуется убрать.
  if($page > 0) {
// Отрезаем кусок текста максимально требуемого размера.
    $left = substr($text, $page*$length, $deviation);
    foreach($delimiters as $delimiter) {
// Находим первое вхождение одной из меток.
      if(($mark = strpos($left, $delimiter)) !== false) {
// Если метка найдена отрезаем с начала текста требуемое число символов
// и завершаем поиск остальных меток.
        $res = substr($res, $mark+strlen($delimiter));
        break;
      }
    }
  }
// Если страница не последняя, то ищем по тексту далее место для разбивки.
  if($page < $count-1) {
// Отрезаем кусок текста максимально требуемого размера от следующей страницы.
    $right = substr($text, ($page+1)*$length, $deviation);
    foreach($delimiters as $delimiter) {
// Находим первое вхождение одной из меток.
      if(($mark = strpos($right, $delimiter)) !== false) {
// Если метка найдена отрезаем с начала текста требуемое число символов,
// добавляем их к текущему тексту и завершаем поиск остальных меток.
        $res = $res . substr($right, 0, $mark+strlen($delimiter));
        break;
      }
    }
  }
  return $res;
}

Приведённый код не без огрехов. Например, он не учитывает тот момент, что исходная разбивка сразу пройдет по нужному месту (концу абзаца, предложения, слова). Не учитывается и использование точки с пробелом в сокращениях (г. – год, т. д. - так далее), а не только в конце предложения. В общем, тут ещё много чего не учитывается, но все-таки читаемость отдельных страниц будет намного лучше, чем при нарезке текста через substr по точным значениям длины страниц.

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

Ограниченный HTML

  • Допустимые HTML-теги: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Строки и абзацы переносятся автоматически.
  • Адреса веб-страниц и email-адреса преобразовываются в ссылки автоматически.
CAPTCHA
А не робот ли вы случайно?
7 + 10 =
Решите эту простую математическую задачу и введите результат. Например, для 1+3, введите 4.