Всё для технического документирования
+7 (925) 328-76-00
Разработка технической документации
Курсы для технических писателей
Программное обеспечение

Алгоритмы в структурированном писательстве: Обработка структурированного текста

07.04.2016

01Статья входит в цикл «Понимание и применение структурированного писательства».

Марк Бейкер продолжает серию своих статей о структурированном писательстве. В очередной статье об алгоритмах, применяемых при использовании структурированного писательства, речь идёт о генерации выходных файлов для вывода на печать или на электронные устройства из структурированного представления, удобного для работы технических писателей.


Структурированное писательство включает в себя отделение контента от форматирования, так как мы выносим контент из домена носителя. Но для того, чтобы опубликовать контент, нам необходимо вернуть его обратно в домен носителя, собрав снова контент с форматированием. Чтобы это сделать, мы обрабатываем структурированный текст с помощью алгоритмов.

Одно из применений структурированного писательства — публикация различных комбинаций контента на различных носителях. Для этого нам могут потребоваться различные алгоритмы создания различных комбинаций контента и различных целевых носителей. Понимание основ этих алгоритмов важно для овладения структурированным писательством, даже если вы планируете программировать эти алгоритмы самостоятельно.

Два в одном: обращение выделения инвариантов

Перемещение контента из домена носителя в домен документа и домен объекта связано с постепенным выделением инвариантов в контенте. Каждый шаг в этом процессе создаёт два сопутствующих объекта: структурированный контент и инвариантный отрывок текста, который был выделен.

Обработка структурированного текста связана со складыванием отрывков вместе с целью создания желаемого вывода; комбинирование структурированного контента с инвариантами, которые были выделены. Если выделение инвариантов переносит контент в домен документа или объекта, то комбинирование контента с инвариантами переносит его в обратном направлении, в домен носителя. Это может означать перенос контента из домена объекта в домен документа или из домена документа в домен носителя, или просто из более абстрактной формы в домене носителя в более конкретную форму (как это будет в нашем первом примере).

Возвращение информации о стилях

Первый пример отделения контента от форматирования, который мы рассматривали как связанный с выделением информации о стилях из этой структуры:

{font: 10pt «Open Sans»}Ящик содержит:

{font: 10pt «Open Sans»}[bullet][tab]Песок

{font: 10pt «Open Sans»}[bullet][tab]Яйца

{font: 10pt «Open Sans»}[bullet][tab]Золото

Мы заменили информацию о стилях именами стилей:

{style: paragraph}Ящик содержит:

{style: bullet-paragraph}Песок

{style: bullet-paragraph}Яйца

{style: bullet-paragraph}Золото

А затем мы определили стили:

paragraph = {font: 10pt «Open Sans»}

bullet-paragraph = {font: 10pt «Open Sans»}[bullet][tab]

Чтобы объединить стили с соответствующими абзацами, мы пишем набор правил, определяющих, как они комбинируются. Мы можем начать с простых правил поиска и замены:

find {style: paragraph}

replace {font: 10pt «Open Sans»}

find {style: bullet-paragraph}

replace {font: 10pt «Open Sans»}[bullet][tab]

Я сказал, что основные алгоритмы разработки предназначались для комбинирования двух источников информации для создания нового. Где расположены эти источники? Первый источник — структурированный текст. Второй источник — определения стилей. В примере выше определения стилей встроены в сами правила. Встраивание одного из двух источников, чтобы они комбинировались в самих правилах — распространённая практика. Хотя в некоторых случаях правила могут брать контент из третьего источника. Мы увидим такие случаи позже.

Результат применения этих правил в том, что мы возвращаем исходный контент:

{font: 10pt «Open Sans»}Ящик содержит:

{font: 10pt «Open Sans»}[bullet][tab]Песок

{font: 10pt «Open Sans»}[bullet][tab]Яйца

{font: 10pt «Open Sans»}[bullet][tab]Золото

Если нам необходимо изменить стили, мы можем применить другой набор правил:

find {style: paragraph}

replace {font: 12pt «Century Schoolbook»}

find {style: bullet-paragraph}

replace {font: 12pt «Century Schoolbook»}[em dash][tab]

В результате применения этих правил мы изменим форматирование исходного контента:

{font: 12pt «Century Schoolbook»}Ящик содержит:

{font: 12pt «Century Schoolbook»}[em dash][tab]Песок

{font: 12pt «Century Schoolbook»}[em dash][tab]Яйца

{font: 12pt «Century Schoolbook»}[em dash][tab]Золото

Правила, основанные на структурах

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

Мы не касались на этом уровне реального механизма, по которому инструмент обработки распознаёт структуры. Мы говорили о том, что делать, когда структура найдена. Так что давайте перепишем наши правила, чтобы подбирать структуры, а не находить буквально строки в тексте:

match paragraph

apply style {font: 12pt «Century Schoolbook»}

match bullet-paragraph

apply style {font: 12pt «Century Schoolbook»}

output «[em dash][tab]»

02Я написал эти правила в том, что называется псевдокодом. Псевдокод — это способ для людей сделать зарисовку алгоритма, чтобы убедиться, что они понимают то, что пытаются сделать, перед тем, как писать, собственно, код. У псевдокода нет формализованной грамматики или синтаксиса. Он подходит для людей, не для компьютеров, так что вы можете использовать любой подход, какой вам нравится, так как это понятно для вашей целевой аудитории. Но псевдокод должен прозрачно выражать набор логических шагов для выполнения чего-либо. Для алгоритмов структурированного писательства он должен делать понятным, как в точности, как вещи связаны друг с другом.

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

Результат применения этих правил — в точности то же самое, что и раньше:

{font: 12pt «Century Schoolbook»}Ящик содержит:

{font: 12pt «Century Schoolbook»}[em dash][tab]Песок

{font: 12pt «Century Schoolbook»}[em dash][tab]Яйца

{font: 12pt «Century Schoolbook»}[em dash][tab]Золото

Единственное отличие – мы опускаем детали того, как распознаются структуры. (Да, код и псевдокод — примеры структурированного писательства в работе!)

Последовательность правил не имеет значения

Возможно, вы обратили внимание – то, что делают эти правила, очень похоже на то, что делают таблицы стилей в таких приложениях как Word или FrameMaker. На самом деле — именно это и делают таблицы стилей. Если вы понимаете таблицы стилей, вы во многом понимаете и то, как работают алгоритмы структурированного писательства.

Заметьте, что когда вы создаёте таблицу стилей в Word или FrameMaker, вы не указываете последовательность, в которой стили будут применяться в документе. То же верно, когда вы создаёте таблицу стилей CSS для Интернета. Таблица стилей — это просто обычный список правил. Последовательность, в которой правила применяются к документу, зависит полностью от последовательности, в которой различные структуры текста появляются в документе.

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

Всё становится чуточку сложнее, когда мы переходим к обработке вложенных структур домена документа и домена объекта, но основная модель набора неупорядоченных правил определения алгоритма преобразования всё ещё применима.

Применение правил в домене документа

Предположим, у нас есть отрывок структурированного текст в домене документа, который содержит эту структуру заголовка:

title: Моби Дик

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

match title

create h1

continue

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

Инструкция продолжения идёт под инструкцией create h1 для того, чтобы показать, что результаты продолжения будут появляться внутри структуры h1.

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

<h1>Моби Дик</h1>

Но предположим, что в нашем источнике существует другая структура внутри заголовка. В этом случае пояснение части текста заголовка:

title: Обзор {Рио Браво}(фильм)

Здесь поясняемый текст выделяется с помощью фигурных скобок, а пояснение само по себе — в обычных скобках сразу после него. Таким образом, пояснение говорит, что слова «Рио Браво» относятся к фильму. (Со временем я объясню эту разметку). Пояснение — структура контента, такая же как структура заголовка, и она вложена внутрь текста заголовка.

Так что мы должны делать с нашим правилом для обработки заголовков, чтобы заставить его иметь дело с пояснением о фильме? Абсолютно ничего. Вместо этого мы пишем отдельное правило для работы пояснений о фильме, и не имеет значения, где они появляются:

match movie

create i

continue

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

<h1>Обзор <i>Рио Браво</i></h1>

Инструкция продолжения — это действительно всё, что нам необходимо, чтобы добавить наши правила, позволяющие работать со вложенными структурами. Они остаются неупорядоченной коллекцией правил, так же, как таблица стилей. (На самом деле XSLT, язык, который обеспечивает эту модель, называет набор правил обработки «таблицей стилей»).

Обработка, основанная на контексте

Когда мы перемещаемся в домен документа, мы можем использовать контекст, чтобы сократить количество необходимых нам структур. Например, тогда как у HTML шесть различных структур заголовков, от h1 до h6, DocBook имеет только одну — title — которая может появляться во множестве различных контекстов. И как мы применяем правильное форматирование к заголовку, основанному на его контексте? Мы создаём различные правила для структуры заголовка в каждом его контексте. Мы выражаем контекст, перечисляя имена родительских структур, разделённых слешами:

match book/title

create h1

continue

match chapter/title

create h2

continue

match section/title

create h3

continue

match figure/title

create h4

continue

Теперь это всё выглядит более умно. Вам не нужно изменять правило movie, чтобы работать с любыми версиями правила title. Предположим, наш заголовок — заголовок секции, примерно так:

section:

title: Обзор {Рио Браво}(фильм)</title>

Если мы обработаем это с помощью своих правил, правило section/title будет запущено, чтобы сработать на структуре заголовка, а правило movie будет запущено, когда в процессе обработки содержимого структуры title появится структура movie, со следующим результатом:

<h3>Обзор <i>Рио Браво</i></h3>

Это основная модель для большинства алгоритмов структурированного писательства. Алгоритм содержит набор правил.

  • Для каждой структуры вы создаёте правило, которое говорит, как преобразовывать эту структуру в нужную вам структуру.
  • Каждое правило определяет создание новых структур и где располагается контент и все вложенные структуры.
  • В каждом правиле вы определяете, где обработчик должен обработать все вложенные структуры и применить все правила, которые к ним применимы.
  • Если вы хотите разные правила для структур, появляющихся в различных контекстах, напишите отдельное правило для каждого контекста.

Почему важно это понимать? Потому что когда мы проходим через процесс абстрагирования инвариантов для перемещения контента в домен документа или домен объекта, то действительно полезно понимать, как эти инварианты будут интегрироваться обратно. Действительно, понимание того, как это работает, может помочь вам распознать инварианты в вашем источнике и дать вам уверенность для их выделения. Написание псевдокода для обработки структуры, которую вы создаёте, может помочь подтвердить, что вы выделили вещи корректно и что структуры, которые вы создаёте, будет легко обрабатывать, и что правила обработки будут понятными, последовательными и надёжными.

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

Обработка структур контейнера

Когда мы переносим контент в домен документа или домен объекта, мы часто создаём структуры-контейнеры для придания контекста. Эти структуры-контейнеры не имеют никаких аналогов в домене носителя, так что мы делаем с ними, когда приходит время публикации? Мы банально используем их, чтобы придать контекст для остальных правил нашей обработки, но что мы делаем с самими контейнерами?

В предыдущем примере контент содержался в структуре section. Так как обработать структуру section?

match section

continue

Да, так вот просто. Просто не выводите никаких новых структур в этом месте. Контейнер секции завершает работу в этом месте, так что просто отбросьте её. Хотя нам всё ещё нужно содержимое внутри неё, так что мы используем инструкцию продолжения, чтобы убедиться, что контент обработался. Если коротко, элемент контейнера — это ящик. Мы выкладываем контент и избавляемся от ящика.

Восстановление выделенного текста

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

Как мы видели, простой пример выделения текста — нумерованные и маркированные списки, где мы выделяем текст номеров и маркеров. Давайте посмотрим, как мы создаём правила, чтобы вставить их обратно.

Допустим, у нас есть документ, содержащий эти два различных типа списков:

paragraph: Чтобы помыть голову:

ordered-list:

list-item:Намылить

list-item:Смыть

list-item:Повторить

paragraph: Ящик содержит:

unordered-list:

list-item:Песок

list-item:Яйца

list-item:Золото

Давайте напишем набор правил, чтобы работать с этим документом. Конвертация этого в HTML-списки не скажет нам многое, так как HTML содержит сами по себе нумерованные списки и списки с буллитами, так что мы создадим инструкции для печати на бумагу. Мы не будем использовать настоящие инструкции для печати (они скучно-детализированные). Вместо этого мы будем использовать такие же условные обозначения для спецификации стилей, которые использовали выше. Правило для paragraph достаточно просто:

match paragraph

apply style {font: 10pt «Century Schoolbook»}

continue

Теперь давайте возьмём ordered-list. Структура упорядоченного списка — всего лишь контейнер, так что нам не требуется создавать структуру вывода для него. Но из-за того, что это упорядоченный список, нам необходимо начать считать номера элементов списка. Это означает, что нам необходима переменная для хранения текущего номера. Мы будем использовать префикс `$`, чтобы показать, что создаём переменную:

match ordered-list

$count=1

continue

Затем правило для каждого элемента ordered-list выведет значение переменной и увеличит её на один:

match ordered-list/list-item

apply style {font: 12pt «Century Schoolbook»}

output $count

output «.[tab]»

$count=$count+1

continue

Каждый раз, когда будет запускаться правило ordered-list/list-item, счёт будет возрастать на единицу, в результате элементы списка будут нумероваться последовательно.

Когда будет встречаться новый нумерованный список, будет запускаться правило ordered-list, сбрасывая счёт на 1.

Это правило не подходит к элементам list-item, являющимся дочерними элементами unordered-list, так что мы должны отделить набор правил для неупорядоченных списков. Из-за того, что unordered-list — просто контейнер и не производит какого-либо форматированного вывода, его правило — просто continue, как мы видели с section выше:

match unordered-list

continue

match ordered-list/list-item

apply style {font: 12pt «Century Schoolbook»}

output «[em dash][tab]»

continue

Применение этих правил произведёт вывод, похожий на этот:

{font: 10pt «Century Schoolbook»}Чтобы помыть голову:

{font: 10pt «Century Schoolbook»}1.[tab]Намылить

{font: 10pt «Century Schoolbook»}2.[tab]Смыть

{font: 10pt «Century Schoolbook»}3.[tab]Посторить

{font: 10pt «Century Schoolbook»}Ящик содержит:

{font: 10pt «Century Schoolbook»}[em dash][tab]Песок

{font: 10pt «Century Schoolbook»}[em dash][tab]Яйца

{font: 10pt «Century Schoolbook»}[em dash][tab]Золото

Обратите внимание, как структура была выровнена, и все абстракции структуры документа были удалены. Мы возвращаемся в домен носителя, с простой структурой, определяющей форматирование и текст.

Обработка в несколько шагов

Нам не всегда нужно применять финальное форматирование к нашему контенту в один шаг. Когда мы отделяли контент от форматирования, мы производили отделение в несколько этапов. Часто желательно соединять его обратно в несколько шагов. Если алгоритмы совершают только один шаг процесса, для упрощения написания и поддержки применяются не только они, мы можем часто повторно использовать некоторые неявные шаги (близкие к домену носителя) для множества различных типов контента в домене документа и домене объекта.

До настоящего времени мы рассматривали примеры из домена носителя и домена документа. Давайте рассмотрим один пример из домена объекта. Мы использовали пример завершения отделения контента от форматирования посредством перемещения маркированного списка из домена документа в домен объекта.

address:

street: 123 Elm Street

town: Smallville

country: USA

code: 12345

Теперь давайте рассмотрим алгоритм (набор правил) для возвращения к домену документа, где он должен выглядеть примерно так:

labeled-list:

list-item:

label: Street

contents: 123 Elm Street

list-item:

label: Town

contents: Smallville

list-item:

label: Country

contents: 123 USA

list-item:

label: Code

contents: 12345

Вот алгоритм (набор правил) для совершения этого преобразования:

match address

create labeled-list

continue

match street

create list-item

create label

output «Street»

create contents

continue

match town

create list-item

create label

output «Town»

create contents

continue

match country

create list-item

create label

output «Country»

create contents

continue

match code

create list-item

create label

output «Code»

create contents

continue

Заметьте, что текст меток, которые были выделены, когда мы переместились в домен объекта, вставляется здесь обратно, и определён в правилах обработки. Когда мы переместили контент из домена носителя в домен документа и далее в домен объекта, мы сначала выделили инвариант форматирования, а затем инвариант текста. В алгоритмах мы вставили обратно текст и форматирование, каждый на разной стадии обработки.

Обработка контента в несколько шагов может сохранить нам множество времени, даже если это может прозвучать алогично. Структура address домена объекта специфична для одного объекта, и мы можем иметь множество таких структур в нашей разметке домена объекта. Но она представлена как структура labeled-list. Маркированный список — это структура домена документа, которую можно использовать для представления любого типа информации, и которая может быть отформатирована для множества различных носителей. Преобразуя структуру адреса в структуру labeled-list, мы избегаем необходимости писать какой-либо код для форматирования структуры address напрямую. Мы можем форматировать адрес корректно для множества носителей с помощью существующих правил форматирования labeled-list.

Обработка на основе запросов

03Основанный на правилах подход, показанный здесь, не единственный способ обрабатывать структурированное писательство. Существует другой подход, который мы можем назвать подходом, основанным на запросах. В этом подходе вы пишете запрос, который поступает в структуру документа и возвращает структуру или набор структур из содержимого документа.

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

Источник: Algorithms in Structured Writing: Processing Structured Text

Тэги: , , ,

< Вернуться к списку публикаций

Облако тегов