Предыдущая версия справа и слеваПредыдущая версияСледующая версия | Предыдущая версия |
wiki:devel:parser [2025/01/16 13:11] – vladpolskiy | wiki:devel:parser [2025/01/17 13:53] (текущий) – [Преобразователь] vladpolskiy |
---|
==== Лексический анализатор ==== | ==== Лексический анализатор ==== |
| |
Определяется в [[xref>inc:parsing:lexer:lexer.php|inc/Parsing/Lexer.php]] | Определяется в [[xref>inc:parsing:lexer:lexer.php|inc/Parsing/Lexer/Lexer.php]] |
| |
В самом общем смысле, он реализует инструмент для управления комплексными регулярными выражениями, где важным является состояние. Анализатор появился из [[http://www.lastcraft.com/simple_test.php|простого теста]], но содержит три модификации (читай: хака ;-)): | В самом общем смысле, он реализует инструмент для управления комплексными регулярными выражениями, где важным является состояние. Анализатор появился из [[https://simpletest.sourceforge.net/en/start-testing.html|Simple Test ]], но содержит три модификации (читай: "хака"): |
| |
* поддержка шаблонов с просмотром вперёд и назад((Прим. переводчика: возможно, речь идёт т. н. **незахватывающем поиске**, подробнее см. литературу по регулярным выражениям, например, Джеффри Фридла «Регулярные выражения: библиотека программиста. Второе издание.» --- СПб.: Питер, 2003, или документацию по PHP --- [[http://ru.php.net/manual/en/ref.pcre.php|ru.php.net/manual/en/ref.pcre.php]].)); | * поддержка шаблонов просмотра назад и просмотра вперед((Прим. переводчика: возможно, речь идёт о т.н. **незахватывающем поиске**, подробнее см. литературу по регулярным выражениям, например, Джеффри Фридла «Регулярные выражения: библиотека программиста. Второе издание.» --- СПб.: Питер, 2003, или документацию по PHP --- [[http://ru.php.net/manual/en/ref.pcre.php|ru.php.net/manual/en/ref.pcre.php]].)); |
* поддержка изменения модификаторов шаблона в пределах шаблона; | * поддержка изменения модификаторов шаблона в пределах шаблона; |
* уведомление обработчика об индексе стартового байта в исходном тексте, когда найдено вхождение. | * уведомление обработчика о начальном индексе байта в необработанном тексте, где был сопоставлен токен. |
| |
Анализатор как целое состоит из трёх основных классов; | Короче говоря, лексер Simple Test действует как инструмент, упрощающий управление регулярными выражениями — вместо гигантских регулярных выражений вы пишете много маленьких/простых. Лексер заботится об их эффективном объединении, а затем предоставляет вам API обратного вызова в стиле SAX , чтобы вы могли писать код для ответа на соответствующие «события». |
| |
* ''Doku_LexerParallelRegex'': позволяет регулярному выражению состоять из множества отдельных шаблонов, каждый шаблон связан с идентифицирующей «меткой» , класс объединяет их в единое регулярное выражение, используя подшаблоны. //Используя анализатор, вам не нужно беспокоится об этом классе//. | В целом Lexer состоит из трех основных классов; |
* ''Doku_LexerStateStack'': реализует простой конечный автомат (state mashine((FIXME Уточнить перевод термина.))), так что анализ может быть «осведомлённым о контексте». //Используя анализатор, вам не нужно беспокоится об этом классе//. | |
* ''Doku_Lexer'': реализует точку доступа для клиентского кода, использующего анализатор. Управляет множеством объектов ParallelRegex, используя StateStack (стэк состояний) для применения корректных объектов ParallelRegex, в зависимости от «контекста». При нахождении «интересного текста» он вызывает функции реализуемого пользователем объекта (обработчика). | * [[xref>inc:parsing:lexer:parallelregex.php|inc/Parsing/Lexer/ParallelRegex]]: позволяет строить регулярные выражения из нескольких отдельных шаблонов, каждый шаблон связан с идентифицирующей «меткой», класс объединяет их в одно регулярное выражение с помощью подшаблонов. //При использовании Lexer вам не нужно беспокоиться об этом классе.// |
| * [[xref>inc:parsing:lexer:statestack.php|inc/Parsing/Lexer/StateStack]]: предоставляет простой конечный автомат, чтобы лексирование могло быть «контекстно-зависимым». //При использовании Lexer вам не нужно беспокоиться об этом классе.// |
| * [[xref>inc:parsing:lexer:lexer.php|inc/Parsing/Lexer/Lexer]]: предоставляет точку доступа для клиентского кода с помощью Lexer. Управляет несколькими экземплярами ParallelRegex, используя StateStack для применения правильного экземпляра ParallelRegex в зависимости от «контекста». При обнаружении «интересного текста» вызывает функции для предоставленного пользователем объекта (Handler). |
| |
=== Необходимость в состояниях === | === Необходимость в состояниях === |
=== API анализатора === | === API анализатора === |
| |
Краткое введение в лексический анализатор можно найти в [[http://www.phppatterns.com/index.php/article/articleview/106/1/2/|Simple Test Lexer Notes]]. Здесь предлагается более подробное описание. | Краткое введение в лексический анализатор можно найти в [[wiki:devel:parser:test:simple_test_lexer_notes|Simple Test Lexer Notes]]. Здесь предлагается более подробное описание. |
| |
Ключевыми методами анализатора являются: | Ключевыми методами анализатора являются: |
== Конструктор == | == Конструктор == |
| |
Принимает ссылку на обработчик, наименование начального режима и (необязательно) логический флаг чувствительности сравнения шаблона к регистру. | Принимает ссылку на объект Handler, имя начального режима, в котором должен запускаться Lexer, и (необязательно) логический флаг, указывающий, должно ли сопоставление с образцом учитывать регистр. |
| |
Пример: | Пример: |
| |
<code php> | <code php> |
$Handler = & new MyHandler(); | $handler = new MyHandler ( ) ; |
$Lexer = & new Doku_Lexer($Handler, 'base', TRUE); | $lexer = new dokuwiki\Lexer\Lexer ( $handler , 'base' , true ) ; |
</code> | </code> |
| |
== addEntryPattern / addExitPattern == | == addEntryPattern / addExitPattern == |
| |
Используется, чтобы зарегистрировать шаблон при входе и выходе из особенного режима обработки. Например: | ''%%addEntryPattern()%%'' в [[xref>inc:parsing:lexer:lexer.php|Lexer.php]] |
| <code php Lexer.php [enable_line_numbers="true", start_line_numbers_at="75"]> |
| public function addEntryPattern($pattern, $mode, $new_mode) |
| { |
| if (! isset($this->regexes[$mode])) { |
| $this->regexes[$mode] = new ParallelRegex($this->case); |
| } |
| $this->regexes[$mode]->addPattern($pattern, $new_mode); |
| } |
| </code> |
| |
<code php> | ''%%addExitPattern()%%'' в [[xref>inc:parsing:lexer:lexer.php|Lexer.php]] |
// arg0: регулярное выражение для сравнения — заметьте, что нет необходимости добавлять ограничители шаблона | <code php Lexer.php [enable_line_numbers="true", start_line_numbers_at="89"]> |
// arg1: наименование режима, где этот шаблон может быть использован | public function addExitPattern($pattern, $mode) |
// arg2: наимерование режима, в который следует войти | { |
$Lexer->addEntryPattern('<file>','base','file'); | if (! isset($this->regexes[$mode])) { |
| $this->regexes[$mode] = new ParallelRegex($this->case); |
| } |
| $this->regexes[$mode]->addPattern($pattern, "__exit"); |
| } |
| </code> |
| [[xref>inc:parsing:lexer:lexer.php|addEntryPattern()]] и [[xref>inc:parsing:lexer:lexer.php|addExitPattern()]] используются для регистрации шаблона для входа и выхода из определенного режима анализа. Например; |
| |
// arg0: регулярное выражение для сравнения | <code php> |
// arg1: наименование режима, из которого следует выйти | // arg0: регулярное выражение для сопоставления — обратите внимание, что нет необходимости добавлять разделители начального/конечного шаблона |
$Lexer->addExitPattern('</file>','file'); | // arg1: имя режима, в котором может использоваться этот шаблон записи |
| // arg2: имя режима для ввода |
| $lexer -> addEntryPattern ( '<file>' , 'base' , 'file' ) ; |
| |
| // arg0: регулярное выражение для сопоставления |
| // arg1: имя режима для выхода |
| $lexer -> addExitPattern ( '</file>' , 'file' ) ; |
</code> | </code> |
| |
Код, приведённый выше, позволяет тэгу %%<file/>%% быть использованный при входе из базового в новый режим (''file''). Если в дальнейшем следует применить режимы, пока анализатор находится в режиме ''file'', они должны быть зарегистрированы с режимом ''file''. | Код, приведённый выше, позволяет тэгу %%<file/>%% быть использованный при входе из базового в новый режим (''file''). Если в дальнейшем следует применить режимы, пока анализатор находится в режиме ''file'', они должны быть зарегистрированы с режимом ''file''. |
| |
**Замечание:** в паттернах не требуется использование ограничителей. | **Замечание:** в паттернах не требуется использование ограничителей (разделителей начала и конца шаблона). |
| |
== addPattern == | == addPattern == |
| |
Используется, чтобы реагировать на дополнительные «вхождения» внутри существующего режима (без переходов). Он принимает паттерн и наименование режима, внутри которого должен использоваться. | ''%%addEntryPattern()%%'' в [[xref>inc:parsing:lexer:lexer.php|Lexer.php]] |
| <code php Lexer.php [enable_line_numbers="true", start_line_numbers_at="57"]> |
| public function addPattern($pattern, $mode = "accept") |
| { |
| if (! isset($this->regexes[$mode])) { |
| $this->regexes[$mode] = new ParallelRegex($this->case); |
| } |
| $this->regexes[$mode]->addPattern($pattern); |
| } |
| </code> |
| |
| ''%%addEntryPattern()%%'' в [[xref>inc:parsing:lexer:parallelregex.php|ParallelRegex.php]] |
| <code php ParallelRegex.php [enable_line_numbers="true", start_line_numbers_at="53"]> |
| public function addPattern($pattern, $label = true) |
| { |
| $count = count($this->patterns); |
| $this->patterns[$count] = $pattern; |
| $this->labels[$count] = $label; |
| $this->regex = null; |
| } |
| </code> |
| |
| [[xref>inc:parsing:lexer:lexer.php|addEntryPattern()]] используется, чтобы реагировать на дополнительные «вхождения» внутри существующего режима (без переходов). Он принимает паттерн и наименование режима, внутри которого должен использоваться. |
| |
Это наиболее наглядно видно из разбора парсером синтаксиса списков. Синтаксис списков выглядит в «Докувики» следующим образом; | Это наиболее наглядно видно из разбора парсером синтаксиса списков. Синтаксис списков выглядит в «Докувики» следующим образом; |
</code> | </code> |
| |
Использование ''%%addPattern%%'' делает возможным сравнивать полный список, одновременно корректно захватывая каждый элемент списка; | Использование ''%%addPattern()%%'' делает возможным сравнивать полный список, одновременно корректно захватывая каждый элемент списка; |
| |
<code php> | <code php> |
// Сравнить начальный элемент списка и изменить режим | // Сопоставляем открывающий элемент списка и меняем режим |
$Lexer->addEntryPattern('\n {2,}[\*]','base','list'); | $lexer -> addEntryPattern ( '\n {2,}[\*]' , 'base' , 'list' ) ; |
| |
| // Сопоставить новые элементы списка, но остаться в режиме списка |
| $lexer -> addPattern ( '\n {2,}[\*]' , 'list' ) ; |
| |
| // Если это перевод строки, который не соответствует указанному выше правилу addPattern, выходим из режима |
| $lexer -> addExitPattern ( '\n' , 'list' ) ; |
| </code> |
| |
// Сравнить новый элемент списка, но остаться в режиме ''list'' | == addSpecialPattern == |
$Lexer->addPattern('\n {2,}[\*]','list'); | |
| |
// Если строка не совпадает с указанным выше правилом addPattern, выйти из режима | ''%%addSpecialPattern()%%'' в [[xref>inc:parsing:lexer:lexer.php|Lexer.php]] |
$Lexer->addExitPattern('\n','list'); | <code php Lexer.php [enable_line_numbers="true", start_line_numbers_at="107"]> |
| public function addSpecialPattern($pattern, $mode, $special) |
| { |
| if (! isset($this->regexes[$mode])) { |
| $this->regexes[$mode] = new ParallelRegex($this->case); |
| } |
| $this->regexes[$mode]->addPattern($pattern, "_$special"); |
| } |
</code> | </code> |
| |
== addSpecialPattern == | [[xref>inc:parsing:lexer:lexer.php|addSpecialPattern()]] используется для входа в новый режим только для совпадения, а затем сразу возвращается в «родительский» режим. Принимает шаблон, имя режима, в котором он может быть применен, и имя «временного» режима для входа для совпадения. Обычно это используется, если вы хотите заменить разметку wiki чем-то другим. Например, чтобы сопоставить смайлик, например %%:-)%%, у вас может быть: |
| |
Используется для входа в новый режим только для сравнения, затем возвращается в «родительский» режим. Принимает в качестве аргументов паттерн, наименование режима, внутри которого его (паттерн) можно применять, и наименование «временного режима", в который нужно войти для сравнения. Обычно может быть использовано, если вы хотите заменить разметку вики на что-нибудь другое. Например, сравнить смайл вроде %%:-)%%; | |
| |
<code php> | <code php> |
$Lexer->addSpecialPattern(':-)','base','smiley'); | $lexer -> addSpecialPattern ( ':-)' , 'base' , 'smiley' ) ; |
</code> | </code> |
| |
== mapHandler == | == mapHandler == |
| |
Позволяет особому режиму быть прикреплённым к методу с разными наименованиями в обработчике. Это может быть полезным, когда различные синтаксические конструкции следует обрабатывать таким образом, как конструкции «Докувики», отключающие другие синтаксические конструкции в особенном текстовом блоке: | ''%%mapHandler()%%'' в [[xref>inc:parsing:lexer:lexer.php|Lexer.php]] |
| <code php Lexer.php [enable_line_numbers="true", start_line_numbers_at="121"]> |
| public function mapHandler($mode, $handler) |
| { |
| $this->mode_handlers[$mode] = $handler; |
| } |
| </code> |
| |
<code php> | [[xref>inc:parsing:lexer:lexer.php|mapHandler()]] позволяет сопоставить определенный именованный режим с методом с другим именем в Handler. Это может быть полезно, когда разный синтаксис должен обрабатываться одинаково, например, синтаксис DokuWiki для отключения другого синтаксиса внутри определенного текстового блока; |
$Lexer->addEntryPattern('<nowiki>','base','unformatted'); | |
$Lexer->addEntryPattern('%%','base','unformattedalt'); | |
$Lexer->addExitPattern('</nowiki>','unformatted'); | |
$Lexer->addExitPattern('%%','unformattedalt'); | |
| |
// Оба вида синтаксических конструкций должны обрабатываться одинаковым образом... | <code php> |
$Lexer->mapHandler('unformattedalt','unformatted'); | $lexer -> addEntryPattern ( '<nowiki>' , 'base' , 'unformatted' ) ; |
| $lexer -> addEntryPattern ( '%%' , 'base' , 'unformattedalt' ) ; |
| $lexer -> addExitPattern ( '</nowiki>' , 'unformatted' ) ; |
| $lexer -> addExitPattern ( '%%' , 'unformattedalt' ) ; |
| |
| // Оба синтаксиса должны обрабатываться одинаково... |
| $lexer -> mapHandler ( 'unformattedalt' , 'unformatted' ) ; |
</code> | </code> |
| |
=== Подшаблоны не допускаются === | === Подшаблоны не допускаются === |
| |
Поскольку анализатор сам использует **подшаблоны** (внутри класса ''ParallelRegex''), код, //использующий// анализатор, этого не может. Иногда это может пригодиться, но, по общему правилу, метод ''%%addPattern%%'' может быть применён для решения проблем, когда обычно применяются подшаблоны. Его преимуществом является упрощение регулярных выражений, таким образом, управления ими. | Поскольку Lexer (анализатор) сам использует **подшаблоны** (внутри класса ''ParallelRegex''), код, //использующий// анализатор, этого не может. Иногда это может пригодиться, но, по общему правилу, метод ''%%addPattern()%%'' может быть применён для решения проблем, когда обычно применяются подшаблоны. Его преимущество в том, что он делает регулярные выражения более простыми и, следовательно, более простыми в управлении. |
| |
**Замечание:** если вы используете в шаблоне круглые скобки, они будут //автоматически// пропущены анализатором. | **Замечание:** если вы используете в шаблоне круглые скобки, они будут //автоматически// пропущены анализатором. |
=== Синтаксические ошибки и состояния === | === Синтаксические ошибки и состояния === |
| |
Для предотвращение «плохо форматируемой» (особенно при пропуске закрывающих тэгов) разметки, приводящей к тому, что анализатор входит в состояние (режим), который он никогда не покинет, может быть полезным использование паттерна просмотра вперёд для проверки наличия закрывающей разметки((Смысл «плохо форматируемый» не применим к парсеру «Докувики» --- он разработан так, чтобы предотвращать случаи, когда пользователь забывает добавить закрывающий тэг некоторой разметки, полностью игнорируя эту разметку.)). Например: | Для предотвращение «плохо форматируемой» (особенно при пропуске закрывающих тэгов) разметки, приводящей к тому, что Lexer (анализатор) входит в состояние (режим), который он никогда не покинет, может быть полезным использование паттерна просмотра вперёд для проверки наличия закрывающей разметки((Смысл «плохо форматируемый» не применим к парсеру «Докувики» --- он разработан так, чтобы предотвращать случаи, когда пользователь забывает добавить закрывающий тэг некоторой разметки, полностью игнорируя эту разметку.)). Например: |
| |
<code php> | <code php> |
// Использование просмотра вперёд во входном шаблоне... | // Использование просмотра вперёд во входном шаблоне... |
$Lexer->addEntryPattern('<file>(?=.*</file>)','base','file'); | // Использовать предпросмотр в шаблоне записи... |
$Lexer->addExitPattern('</file>','file'); | $lexer -> addEntryPattern ( '<file>(?=.*</file>)' , 'base' , 'file' ) ; |
| $lexer -> addExitPattern ( '</file>' , 'file' ) ; |
</code> | </code> |
| |
Входной шаблон проверяет, может ли он найти закрывающий тэг ''%%</file>%%'', до входа в состояние. | Шаблон входа проверяет, может ли он найти закрывающий ''</file>'' тег, прежде чем войти в состояние. |
| |
| |
==== Обработчик ==== | ==== Handler (Обработчик) ==== |
| |
Определяется в ''inc/parser/handler.php''. | Определено в [[xref>inc:parser:handler.php|inc/parser/handler.php]] и папке ''inc/Parsing/Handler'' |
| |
Обработчик --- это класс, реализующий методы, которые вызываются лексическим анализатором, когда тот обнаруживает вхождения. Затем он «тонко преобразует» вхождения в последовательность инструкций, готовых для передачи преобразователю. | <diagram> |
| |AAA||AAA{text-align:left;border-color:white}={{fa>folder?}} Handler |
| |)|BBB|BBB{text-align:left;border-color:white}={{fa>file?}} AbstractRewriter.php |
| |)|CCC|CCC{text-align:left;border-color:white}={{fa>file?}} Block.php |
| |)|DDD|DDD{text-align:left;border-color:white}={{fa>file?}} CallWriter.php |
| |)|EEE|EEE{text-align:left;border-color:white}={{fa>file?}} Nest.php |
| |)|FFF|FFF{text-align:left;border-color:white}={{fa>file?}} Lists.php |
| |)|GGG|GGG{text-align:left;border-color:white}={{fa>file?}} CallWriterInterface.php |
| |)|KKK|KKK{text-align:left;border-color:white}={{fa>file?}} Preformatted.php |
| |)|LLL|LLL{text-align:left;border-color:white}={{fa>file?}} Quote.php |
| |)|MMM|MMM{text-align:left;border-color:white}={{fa>file?}} ReWriterInterface.php |
| |`|NNN|NNN{text-align:left;border-color:white}={{fa>file?}} Table.php |
| </diagram> |
| |
Обработчик как целое состоит из следующих классов: | Handler — это класс, предоставляющий методы, которые вызывает Lexer, когда он сопоставляет токены. Затем он «тонко настраивает» токены в последовательность инструкций, готовых для Renderer. |
| |
* ''Doku_Handler'': все вызовы из анализатора адресованы этому классу. Для каждого режима, зарегистрированного анализатором, будет соответствующий ему метод в обработчике. | Обработчик в целом содержит следующие классы: |
* ''Doku_Handler_CallWriter'': реализует «прокладку» между массивом инструкций (массив ''Doku_Handler::$calls'') и методами обработчика, //записывающими// эти инструкции. Пока идёт лексический анализ, он будет временно перемещён другими объектами, вроде ''Doku_Handler_List''. | |
* ''Doku_Handler_List'': отвечает за трансформацию перечня вхождений в инструкции, пока идёт лексический разбор. | |
* ''Doku_Handler_Preformatted'': отвечает за трансформацию предварительно отформатированных вхождений (врезки в «Докувики») в инструкции, пока идёт лексический разбор. | |
* ''Doku_Handler_Quote'': отвечает за трансформацию вхождений цитат (текста, начинающего с одной или более «>») в инструкции, пока идёт лексический разбор. | |
* ''Doku_Handler_Table'': отвечает за трансформацию вхождений таблиц в инструкции, пока идёт лексический разбор. | |
* ''Doku_Handler_Section'': отвечает за вставку инстукций секций, основываясь на позиции инструкций заголовков, только когда лексический анализ завершён --- повторяется однократно. | |
* ''Doku_Handler_Block'': отвечает за вставку инструкций 'p_open' и 'p_close', будучи осведомлённым об инструкциях 'block level' instructions, только когда лексический анализ завершён (т. е. он повторяется однократно посредством, выдавая полный перечень инструкций и вставляет дополнитльеный инструкции). | |
* ''Doku_Handler_Toc'': отвечает за добавление инструкций таблицы содержания в начало последовательности, основываясь на инструкциях заголовка, только когда лексический анализ завершён (т. е. он повторяется однократно посредством, выдавая полный перечень инструкций и вставляет дополнитльеный инструкции). | |
| |
=== Методы вхождений обработчика === | * [[wiki:devel:parser:doku_handler|Doku_Handler]]: все вызовы из Lexer производятся в этот класс. Для каждого режима, зарегистрированного в Lexer, будет соответствующий метод в Handler |
| |
Обработчик должен реализовывать методы, соответствующие режимам, зарегистрированным анализатором (подразумевается метод ''%%mapHandler()%%'' анализатора --- см. выше). | ''Doku_Handler'' в [[xref>inc:extension:syntaxplugin.php|/dokuwiki/inc/Extension/SyntaxPlugin.php]] |
| <code php SyntaxPlugin.php > |
| 6 use Doku_Handler; |
| 57 * @see Doku_Handler_Block |
| 75 * @param Doku_Handler $handler The Doku_Handler object |
| 77 * @param Doku_Handler $handler The Doku_Handler object |
| 80 abstract public function handle($match, $state, $pos, Doku_Handler $handler); |
| </code> |
| ''Doku_Handler'' в [[xref>inc:parsing:parser.php|/dokuwiki/inc/Parsing/Parser.php]] |
| <code php Parser.php > |
| 6 use Doku_Handler; |
| 17 /** @var Doku_Handler */ |
| 32 * @param Doku_Handler $handler |
| 34 public function __construct(Doku_Handler $handler) |
| </code> |
| ''Doku_Handler'' в [[xref>inc:parser:handler.php|/dokuwiki/inc/parser/handler.php]] |
| <code php handler.php > |
| 16 * Class Doku_Handler |
| 18 class Doku_Handler { |
| 40 * Doku_Handler constructor. |
| 795 $link[1] = Doku_Handler_Parse_Media($link[1]); |
| 878 $p = Doku_Handler_Parse_Media($match); |
| 1023 function Doku_Handler_Parse_Media($match) { |
| </code> |
| |
| * [[xref>inc:parsing:handler:callwriter.php|CallWriter]]: обеспечивает слой между массивом инструкций (массив ''Doku_Handler::$calls'') и методами Handler, //записывающими// эти инструкции. Пока идёт лексический анализ, он будет временно перемещён другими объектами, вроде ''dokuwiki\Parsing\Handler\List''. |
| * [[xref>inc:parsing:handler:lists.php|List]]: отвечает за преобразование токенов списка в инструкции, пока выполняется лексический анализ |
| * [[xref>inc:parsing:handler:quote.php|Quote]]: отвечает за преобразование токенов ''blockquote'' (текст, начинающийся с одного или нескольких >) в инструкции, пока выполняется лексический анализ |
| * [[xref>inc:parsing:handler:table.php|Table]]: отвечает за преобразование токенов таблицы в инструкции, пока выполняется лексический анализ |
| * [[xref>inc:parsing:handler:block.php|Block]]: отвечает за вставку инструкций «p_open» и «p_close», при этом отслеживая инструкции «уровня блока», после завершения всего лексического анализа (т.е. выполняет цикл один раз по всему списку инструкций и вставляет больше инструкций) |
| * [[xref>inc:parsing:handler:abstractrewriter.php|AbstractRewriter]]: расширено Preformattedи Nest… FIXME |
| * [[xref>inc:parsing:handler:nest.php|Nest]]: ...FIXME |
| * [[xref>inc:parsing:handler:preformatted.php|Preformatted]]: отвечает за преобразование предварительно отформатированных токенов (отступ в тексте dokuwiki) в инструкции, пока лексический анализ еще выполняется |
| |
| === Методы токенов обработчиков === |
| |
| Обработчик должен предоставлять методы, названные в соответствии с режимами, зарегистрированными в лексическом анализаторе (имея в виду %%mapHandler()%% метод лексического анализатора — см. выше). |
| |
Например, если вы зарегистировали в анализаторе режим ''file'' наподобие: | Например, если вы зарегистрировали режим файла с помощью Lexer, например: |
| |
<code php> | <code php> |
$Lexer->addEntryPattern('<file>(?=.*</file>)','base','file'); | $lexer->addEntryPattern('<file>(?=.*</file>)','base','file'); |
$Lexer->addExitPattern('</file>','file'); | $lexer->addExitPattern('</file>','file'); |
</code> | </code> |
| |
Обработчику требуется метод вроде: | Обработчику понадобится такой метод: |
| |
<code php> | <code php> |
class Doku_Handler { | class Doku_Handler { |
| |
/** | /** |
* @строковый параметр match содержит текст, который был обнаружен | * @param string match содержит совпавший текст |
* @целочисленный параметр state - тип совпадения (см. ниже) | * @param int state - тип найденного соответствия (см. ниже) |
* @целочисленный параметр pos - индекс байта, где было найдено совпадение | * @param int pos - индекс байта, где было найдено совпадение |
*/ | */ |
function file($match, $state, $pos) { | public function file($match, $state, $pos) { |
return TRUE; | return true; |
} | } |
} | } |
</code> | </code> |
| |
**Замечание:** метод обработчика //обязан// вернуть «TRUE» или анализатор будет немедленно остановлен. Подобное поведение может быть полезным, когда встречаются другие проблемы обработки, но в парсере «Докувики» все методы обработчика //всегда// возвращают «TRUE». | **Примечание:** метод Handler //должен// возвращать **true**, иначе Lexer немедленно остановится. Такое поведение может быть полезным при работе с другими типами проблем синтаксического анализа, но для парсера DokuWiki все методы Handler //всегда// будут возвращать **true**. |
| |
Аргументы, реализумые методом обработчика; | Аргументы, реализумые методом обработчика; |
В случае со списками, это требует помощи класса ''Doku_Handler_List'', который принимает вхождения, заменяя их на корректные инструкции для Преобразователя. | В случае со списками, это требует помощи класса ''Doku_Handler_List'', который принимает вхождения, заменяя их на корректные инструкции для Преобразователя. |
| |
==== Парсер ==== | ==== Parser (Парсер) ==== |
| |
Парсер играет роль переднего рубежа для внешнего кода и устанавливает для лексического анализатора паттерны и режимы, описывающие синтаксис «Докувики». | Определено в [[xref>inc:parsing:parser.php|/dokuwiki/inc/Parsing/Parser.php]] и [[xref>inc:parser:parser.php|/dokuwiki/inc/parser/parser.php]]. |
| |
Использование парсера в общем случае выглядит следущим образом: | dokuwiki [[xref>inc:parsing:parser.php|\Parsing\Parser]] действует как интерфейс для внешнего кода и настраивает Lexer с помощью шаблонов и режимов, описывающих синтаксис DokuWiki. |
| |
| Использование парсера обычно выглядит так: |
| |
<code php> | <code php> |
// Создать парсер | // Создаем обработчик Handler |
$Parser = & new Doku_Parser(); | $handler = new Doku_Handler(); |
| |
// Создать обработчик и поместить в парсер | // Создаем парсер с обработчиком |
$Parser->Handler = & new Doku_Handler(); | $parser = new dokuwiki\Parsing\Parser($handler); |
| |
// Добавить требуемые синтаксические режимы в парсер | // Добавить требуемые режимы синтаксиса в парсер |
$Parser->addMode('footnote',new Doku_Parser_Mode_Footnote()); | $parser->addMode('footnote', new dokuwiki\Parsing\ParserMode\Footnote()); |
$Parser->addMode('hr',new Doku_Parser_Mode_HR()); | $parser->addMode('hr', new dokuwiki\Parsing\ParserMode\Hr()); |
$Parser->addMode('unformatted',new Doku_Parser_Mode_Unformatted()); | $parser->addMode('unformatted', new dokuwiki\Parsing\ParserMode\Unformatted()); |
# etc. | # etc. |
| |
$doc = file_get_contents('wikipage.txt.'); | $doc = file_get_contents('wikipage.txt.'); |
$instructions = $Parser->parse($doc); | $instructions = $parser->parse($doc); |
</code> | </code> |
| |
Более подробные примеры приведены ниже. | Более подробные примеры приведены ниже. |
| |
В целом, парсер также содержит классы, предсталяющие по отдельности каждый из доступных режимов, базовым классом для всех них является ''Doku_Parser_Mode''. Поведение этих режимов лучше всего понять, посмотрев на примеры добавления синтаксиса ниже в этом документе. | В целом Parser также содержит классы, представляющие каждый доступный режим синтаксиса, базовым классом для всех них является [[xref>inc:parsing:parsermode:abstractmode.php|dokuwiki\Parsing\ParserMode\AbstractMode]]. Поведение этих режимов лучше всего понять, рассмотрев примеры добавления синтаксиса далее в этом документе. |
| |
//Причиной// для представления режимов как классов является желание избежать повторяющихся вызовов методов анализатора. Без них было бы необходимо упорно разрабатывать каждое правило паттерна для каждого режима, в котором паттерн мог бы сравниваться, например, для регистрации единого правила паттерна для синтаксиса ссылок ВерблюжьегоСтиля (CamelCase) требовалось бы что-то вроде: | Причина представления режимов с помощью классов заключается в том, чтобы избежать повторных вызовов методов Lexer. Без них пришлось бы жестко кодировать каждое правило шаблона для каждого режима, в котором может быть сопоставлен шаблон, например, регистрация одного правила шаблона для синтаксиса ссылок CamelCase потребовала бы чего-то вроде: |
| |
<code php> | <code php> |
$Lexer->addSpecialPattern('\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b','base','camelcaselink'); | $lexer->addSpecialPattern('\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b', 'base', 'camelcaselink'); |
$Lexer->addSpecialPattern('\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b','footnote','camelcaselink'); | $lexer->addSpecialPattern('\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b', 'footnote', 'camelcaselink'); |
$Lexer->addSpecialPattern('\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b','table','camelcaselink'); | $lexer->addSpecialPattern('\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b', 'table', 'camelcaselink'); |
$Lexer->addSpecialPattern('\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b','listblock','camelcaselink'); | $lexer->addSpecialPattern('\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b', 'listblock', 'camelcaselink'); |
$Lexer->addSpecialPattern('\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b','strong','camelcaselink'); | $lexer->addSpecialPattern('\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b', 'strong', 'camelcaselink'); |
$Lexer->addSpecialPattern('\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b','underline','camelcaselink'); | $lexer->addSpecialPattern('\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b', 'underline', 'camelcaselink'); |
// etc. | // и т.д. |
</code> | </code> |
| |
Каждый режим, который позволяет содержать ссылки ВерблюжьегоСтиля должен был быть явно указан. | Каждый режим, которому разрешено содержать ссылки CamelCase, должен быть явно назван. |
| |
Вместо этого используется единый класс вроде: | Вместо того, чтобы жестко кодировать это, вместо этого это реализовано с использованием одного класса, например: |
| |
<code php> | <code php> |
class Doku_Parser_Mode_CamelCaseLink extends Doku_Parser_Mode { | namespace dokuwiki\Parsing\ParserMode; |
| |
function connectTo($mode) { | class CamelCaseLink extends AbstractMode { |
| |
| public function connectTo($mode) { |
$this->Lexer->addSpecialPattern( | $this->Lexer->addSpecialPattern( |
'\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b',$mode,'camelcaselink' | '\b[A-Z]+[a-z]+[A-Z][A-Za-z]*\b', $mode, 'camelcaselink' |
); | ); |
} | } |
| |
} | } |
</code> | </code> |
| |
При установке параметров лексического анализатора, парсер вызывает метод ''%%connectTo%%'' объекта ''Doku_Parser_Mode_CamelCaseLink'' для любого режима, который принимает синтаксис ВерблюжьегоСтиля. | При настройке лексического анализатора парсер вызывает ''%%connectTo()%%'' метод объекта ''dokuwiki\Parsing\ParserMode\CamelCaseLink'' для каждого другого режима, который принимает синтаксис CamelCase (некоторым такой ''%%<code />%%'' синтаксис не нравится). |
| |
| За счет усложнения понимания настройки лексического анализатора это позволяет сделать код более гибким при добавлении нового синтаксиса. |
| |
Это позволяет коду быть более гибким при добавлении новых синтаксических конструкций. | |
| |
==== Формат данных инструкций ==== | ==== Формат данных инструкций ==== |
| |
Следующее показывает пример исходного текста вики и соответствующий вывод парсера: | [[wiki:plugin:parserarray|Плагин Parserarray]] — это экспортный рендерер, который показывает инструкции для текущей страницы. Он может помочь вам понять формат данных. Ниже показан пример сырого текста вики и соответствующий вывод парсера; |
| |
Исходный текст (содержит таблицу): | Исходный текст (содержит таблицу): |
| |
<code> | <code> |
Array | Array( |
( | [0] => Array( |
[0] => Array | |
( | |
[0] => document_start | [0] => document_start |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 0 | [2] => 0 |
) | ) |
| [1] => Array( |
[1] => Array | |
( | |
[0] => p_open | [0] => p_open |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 0 | [2] => 0 |
) | ) |
| [2] => Array( |
[2] => Array | |
( | |
[0] => cdata | [0] => cdata |
[1] => Array | [1] => Array( |
( | |
[0] => | [0] => |
| |
abc | abc |
) | ) |
| |
[2] => 0 | [2] => 0 |
) | ) |
| [3] => Array( |
[3] => Array | |
( | |
[0] => p_close | [0] => p_close |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 5 | [2] => 5 |
) | ) |
| [4] => Array( |
[4] => Array | |
( | |
[0] => table_open | [0] => table_open |
[1] => Array | [1] => Array( |
( | |
[0] => 3 | [0] => 3 |
[1] => 2 | [1] => 2 |
) | ) |
| |
[2] => 5 | [2] => 5 |
) | ) |
| [5] => Array( |
[5] => Array | |
( | |
[0] => tablerow_open | [0] => tablerow_open |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 5 | [2] => 5 |
) | ) |
| [6] => Array( |
[6] => Array | |
( | |
[0] => tablecell_open | [0] => tablecell_open |
[1] => Array | [1] => Array( |
( | |
[0] => 1 | [0] => 1 |
[1] => left | [1] => left |
) | ) |
| |
[2] => 5 | [2] => 5 |
) | ) |
| [7] => Array( |
[7] => Array | |
( | |
[0] => cdata | [0] => cdata |
[1] => Array | [1] => Array( |
( | |
[0] => Row 0 Col 1 | [0] => Row 0 Col 1 |
) | ) |
| |
[2] => 7 | [2] => 7 |
) | ) |
| [8] => Array( |
[8] => Array | |
( | |
[0] => cdata | [0] => cdata |
[1] => Array | [1] => Array( |
( | |
[0] => | [0] => |
) | ) |
| |
[2] => 19 | [2] => 19 |
) | ) |
| [9] => Array( |
[9] => Array | |
( | |
[0] => tablecell_close | [0] => tablecell_close |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 23 | [2] => 23 |
) | ) |
| [10] => Array( |
[10] => Array | |
( | |
[0] => tablecell_open | [0] => tablecell_open |
[1] => Array | [1] => Array( |
( | |
[0] => 1 | [0] => 1 |
[1] => left | [1] => left |
) | ) |
| |
[2] => 23 | [2] => 23 |
) | ) |
| [11] => Array( |
[11] => Array | |
( | |
[0] => cdata | [0] => cdata |
[1] => Array | [1] => Array( |
( | |
[0] => Row 0 Col 2 | [0] => Row 0 Col 2 |
) | ) |
| |
[2] => 24 | [2] => 24 |
) | ) |
| [12] => Array( |
[12] => Array | |
( | |
[0] => cdata | [0] => cdata |
[1] => Array | [1] => Array( |
( | |
[0] => | [0] => |
) | ) |
| |
[2] => 36 | [2] => 36 |
) | ) |
| [13] => Array( |
[13] => Array | |
( | |
[0] => tablecell_close | [0] => tablecell_close |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 41 | [2] => 41 |
) | ) |
| [14] => Array( |
[14] => Array | |
( | |
[0] => tablecell_open | [0] => tablecell_open |
[1] => Array | [1] => Array( |
( | |
[0] => 1 | [0] => 1 |
[1] => left | [1] => left |
) | ) |
| |
[2] => 41 | [2] => 41 |
) | ) |
| [15] => Array( |
[15] => Array | |
( | |
[0] => cdata | [0] => cdata |
[1] => Array | [1] => Array( |
( | |
[0] => Row 0 Col 3 | [0] => Row 0 Col 3 |
) | ) |
| |
[2] => 42 | [2] => 42 |
) | ) |
| [16] => Array( |
[16] => Array | |
( | |
[0] => cdata | [0] => cdata |
[1] => Array | [1] => Array( |
( | |
[0] => | [0] => |
) | ) |
| |
[2] => 54 | [2] => 54 |
) | ) |
| [17] => Array( |
[17] => Array | |
( | |
[0] => tablecell_close | [0] => tablecell_close |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 62 | [2] => 62 |
) | ) |
| [18] => Array( |
[18] => Array | |
( | |
[0] => tablerow_close | [0] => tablerow_close |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 63 | [2] => 63 |
) | ) |
| [19] => Array( |
[19] => Array | |
( | |
[0] => tablerow_open | [0] => tablerow_open |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 63 | [2] => 63 |
) | ) |
| [20] => Array( |
[20] => Array | |
( | |
[0] => tablecell_open | [0] => tablecell_open |
[1] => Array | [1] => Array( |
( | |
[0] => 1 | [0] => 1 |
[1] => left | [1] => left |
) | ) |
| |
[2] => 63 | [2] => 63 |
) | ) |
| [21] => Array( |
[21] => Array | |
( | |
[0] => cdata | [0] => cdata |
[1] => Array | [1] => Array( |
( | |
[0] => Row 1 Col 1 | [0] => Row 1 Col 1 |
) | ) |
| |
[2] => 65 | [2] => 65 |
) | ) |
| [22] => Array( |
[22] => Array | |
( | |
[0] => cdata | [0] => cdata |
[1] => Array | [1] => Array( |
( | |
[0] => | [0] => |
) | ) |
| |
[2] => 77 | [2] => 77 |
) | ) |
| [23] => Array( |
[23] => Array | |
( | |
[0] => tablecell_close | [0] => tablecell_close |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 81 | [2] => 81 |
) | ) |
| [24] => Array( |
[24] => Array | |
( | |
[0] => tablecell_open | [0] => tablecell_open |
[1] => Array | [1] => Array( |
( | |
[0] => 1 | [0] => 1 |
[1] => left | [1] => left |
) | ) |
| |
[2] => 81 | [2] => 81 |
) | ) |
| [25] => Array( |
[25] => Array | |
( | |
[0] => cdata | [0] => cdata |
[1] => Array | [1] => Array( |
( | |
[0] => Row 1 Col 2 | [0] => Row 1 Col 2 |
) | ) |
| |
[2] => 82 | [2] => 82 |
) | ) |
| [26] => Array( |
[26] => Array | |
( | |
[0] => cdata | [0] => cdata |
[1] => Array | [1] => Array( |
( | |
[0] => | [0] => |
) | ) |
| |
[2] => 94 | [2] => 94 |
) | ) |
| [27] => Array( |
[27] => Array | |
( | |
[0] => tablecell_close | [0] => tablecell_close |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 99 | [2] => 99 |
) | ) |
| [28] => Array( |
[28] => Array | |
( | |
[0] => tablecell_open | [0] => tablecell_open |
[1] => Array | [1] => Array( |
( | |
[0] => 1 | [0] => 1 |
[1] => left | [1] => left |
) | ) |
| |
[2] => 99 | [2] => 99 |
) | ) |
| [29] => Array( |
[29] => Array | |
( | |
[0] => cdata | [0] => cdata |
[1] => Array | [1] => Array( |
( | |
[0] => Row 1 Col 3 | [0] => Row 1 Col 3 |
) | ) |
| |
[2] => 100 | [2] => 100 |
) | ) |
| [30] => Array( |
[30] => Array | |
( | |
[0] => cdata | [0] => cdata |
[1] => Array | [1] => Array( |
( | |
[0] => | [0] => |
) | ) |
| |
[2] => 112 | [2] => 112 |
) | ) |
| [31] => Array( |
[31] => Array | |
( | |
[0] => tablecell_close | [0] => tablecell_close |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 120 | [2] => 120 |
) | ) |
| [32] => Array( |
[32] => Array | |
( | |
[0] => tablerow_close | [0] => tablerow_close |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 121 | [2] => 121 |
) | ) |
| [33] => Array( |
[33] => Array | |
( | |
[0] => table_close | [0] => table_close |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 121 | [2] => 121 |
) | ) |
| [34] => Array( |
[34] => Array | |
( | |
[0] => p_open | [0] => p_open |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 121 | [2] => 121 |
) | ) |
| [35] => Array( |
[35] => Array | |
( | |
[0] => cdata | [0] => cdata |
[1] => Array | [1] => Array( |
( | |
[0] => def | [0] => def |
| |
) | ) |
| |
[2] => 122 | [2] => 122 |
) | ) |
| [36] => Array( |
[36] => Array | |
( | |
[0] => p_close | [0] => p_close |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 122 | [2] => 122 |
) | ) |
| [37] => Array( |
[37] => Array | |
( | |
[0] => document_end | [0] => document_end |
[1] => Array | [1] => Array() |
( | |
) | |
[2] => 122 | [2] => 122 |
) | ) |
| |
) | ) |
</code> | </code> |
| |
Верхний уровень массива --- это просто список. Каждый из его дочерних элементов описывает возвратную функцию, которая будет запущена под преобразователем (см. описание [[#преобразователь|преобразователя]] ниже), также как и индекс байта исходного текста, где был найден особенный «элемент» синтаксиса вики. | Верхний уровень массива --- это просто список. Каждый из его дочерних элементов описывает возвратную функцию, которая будет запущена под преобразователем (см. описание [[#Renderer (преобразователь)|Renderer]] ниже), также как и индекс байта исходного текста, где был найден особенный «элемент» синтаксиса вики. |
| |
=== Единственная инструкция === | === Единственная инструкция === |
| |
) | ) |
| |
[2] => 122 | [2] => 122 |
) | ) |
</code> | </code> |
| |
Первый элемент (индекс 0 ) --- это наименование метода или функции, исполняемой преобразователем. | Первый элемент (индекс 0) — это имя метода или функции в Renderer, которую необходимо выполнить. |
| |
Второй элемент (индекс 1) --- сам является массивом, каждый из элементов //которого// будет аргументом вызываемого метода преобразователя. | Второй элемент (индекс 1) сам по себе является массивом, каждый из элементов которого является аргументом для метода Renderer, который будет вызван. |
| |
В этом случае это будет единственный аргумент со значением ''%%"def\n"%%'', так что вызов метода будет: | В этом случае имеется один аргумент со значением ''%%"def\n"%%'', поэтому вызов метода будет выглядеть так: |
| |
<code php> | <code php> |
</code> | </code> |
| |
Третий элемент (индекс 2) --- является индексом байта первого символа, на котором «сработает» эта инструкция в исходном тексте. Он должен быть точно таким же, как и значение, возвращённое PHP-функцией «[[phpfn>strpos]]». Это может использоваться для обнаружения секции исходного текста вики, основанной на позиции сгенерированной инструкции (позже будет пример). | Третий элемент (индекс 2) — это индекс байта первого символа, который «запустил» эту инструкцию в необработанном текстовом документе. Он должен быть таким же, как значение, возвращаемое функцией PHP [[phpfn>strpos]]. Это можно использовать для извлечения разделов необработанного текста вики на основе позиций сгенерированных из него инструкций (пример ниже). |
| |
**Замечание:** метод ''parse'' парсера разбивает исходный вики текст на предыдущий и последующий символы, чтобы гарантировать корректный выход анализатора из состояний, так что вам требуется вычесть единицу из индекса байта, чтобы получить корректную позицию оригинального исходного вики-текста. Также парсер нормализует строки под стиль Unix’а (т. е. все''%%\r\n%%'' становятся ''%%\n%%''), так что документ, который видит анализатор, может быть меньше, чем тот, который вы в действительности загрузили. | **Примечание**: Метод парсера ''parse'' дополняет необработанный текст вики предшествующим и последующим символом перевода строки, чтобы гарантировать корректный выход определенных состояний лексера, поэтому вам может потребоваться вычесть 1 из индекса байта, чтобы получить правильное местоположение в исходном необработанном тексте вики. Парсер также нормализует переводы строк в соответствии со стилем Unix (т. е. все ''%%\r\n%%'' становятся ''%%\n%%''), поэтому документ, который видит лексер, может быть меньше того, который вы ему фактически дали. |
| |
Пример массив инструкций страницы с описанием [[wiki:syntax|синтаксиса]] --- [[devel:parser:sample_instructions]]. | Пример массив инструкций страницы с описанием [[wiki:syntax|синтаксиса]] можно найти [[wiki:devel:parser:sample_instructions|здесь]]. |
| |
==== Преобразователь ==== | ==== Renderer (преобразователь) ==== |
| |
Преобразователь --- это класс (или коллекция функций), определяемый вами. Его интерфейс описан в файле ''inc/parser/renderer.php'' и выглядит так: | Renderer (преобразователь) --- это класс (или коллекция функций), определяемый вами. Его интерфейс описан в файле ''inc/parser/renderer.php'' и выглядит так: |
| |
<code php> | <code php> |
class Doku_Renderer { | class Doku_Renderer { |
| |
// snip | // вырезка |
| |
function header($text, $level) {} | public function header($text, $level) {} |
| |
function section_open($level) {} | public function section_open($level) {} |
| |
function section_close() {} | public function section_close() {} |
| |
function cdata($text) {} | public function cdata($text) {} |
| |
function p_open() {} | public function p_open() {} |
| |
function p_close() {} | public function p_close() {} |
| |
function linebreak() {} | public function linebreak() {} |
| |
function hr() {} | public function hr() {} |
| |
// snip | // вырезка |
} | } |
</code> | </code> |
| |
Он используется для документирования преобразователя, хотя также может быть расширен, если вы захотите написать преобразователь, который лишь перехватывает определённые вызовы. | Он используется для документирования Renderer, хотя его также можно расширить, если вы хотите написать Renderer, который захватывает только определенные вызовы. |
| |
Основной принцип того, как инструкции, возвращаемые парсером, используются преобразователем, близок по смыслу к [[wp>ru:SAX|SAX XML API]] --- инструкции являются перечнем имён функций / методов и их аргуменов. Каждая инструкция может быть вызвана через преобразователь (т. е. реализуемые им методы являются [[wp>ru:Callback_(программирование)|обратными]]). В отличие от «SAX API», где доступно совсем немного, достаточно общих, обратно вызываемых методов (например, tag_start, tag_end, cdata и т. д.), преобразователь определяет более точную API, где методы обычно соответствуют один-к-одному действию по генерации выходных данных. | Основной принцип того, как инструкции, возвращаемые парсером, используются против Renderer, аналогичен понятию [[wp>Simple_API_for_XML|SAX XML API]] - инструкции представляют собой список имен функций/методов и их аргументов. Проходя по списку инструкций, каждая инструкция может быть вызвана против Renderer (т. е. методы, предоставляемые Renderer, являются [[wp>Callback_(computer_science)|callbacks]]). Unlike the SAX API, where only a few, fairly general, callbacks are available (e.g. tag_start, tag_end, cdata etc.). В отличие от SAX API , где доступно только несколько, довольно общих, обратных вызовов (например, tag_start, tag_end, cdata и т. д.), Renderer определяет более явный API , где методы обычно соответствуют один к одному акту генерации вывода. В разделе Renderer, показанном выше, методы ''p_open'' и ''p_close'' будут использоваться для вывода тегов ''%%<p>%%'' и ''%%</p>%%'' в XHTML, соответственно, в то время как ''header'' функция принимает два аргумента — некоторый текст для отображения и «уровень» заголовка, поэтому вызов типа ''%%header('Some Title', 1)%%'' будет выведен в XHTML типа ''%%<h1>Some Title</h1>%%''. |
| |
Во фрагменте преобразователя, показанном выше методы ''p_open'' и ''p_close'' будут использованы для вывода тэгов ''%%<p>%%'' и ''%%</p>%%'' в XHTML, соответственно, в то время, как функция ''header'' принимает два аргумента --- некоторый текст для отображения и «уровень» заголовка, так что вызов типа ''%%header('Some Title',1)%%'' выведет в XHTML ''%%<h1>Some Title</h1>%%''. | === Вызов рендерера с инструкциями === |
| |
=== Вызов преобразователя через инструкции === | Клиентскому коду, использующему Parser, остается выполнить список инструкций для Renderer. Обычно это делается с помощью функции PHP [[phpfn>call_user_func_array()]] function. Например; |
| |
К **клиентскому коду** относится использование парсера для выполнения перечня инструкций через преобразователь. Обычно это делается использованием php-функции «[[phpfn>call_user_func_array]]». Например: | |
| |
<code php> | <code php> |
// Получить перечень инструкций из парсера | // Получить список инструкций от парсера |
$instructions = $Parser->parse($rawDoc); | $instructions = $parser->parse($rawDoc); |
| |
// Создать преобразователь | // Создаем рендерер |
$Renderer = & new Doku_Renderer_XHTML(); | $renderer = new Doku_Renderer_xhtml(); |
| |
// Пройтись по всем инструкциям | // Проходим по инструкциям |
foreach ( $instructions as $instruction ) { | foreach ($instructions as $instruction) { |
| // Выполняем обратный вызов для Renderer |
// Выполнить инструкции через преобразователь | call_user_func_array([$renderer, $instruction[0]], $instruction[1]); |
call_user_func_array(array(&$Renderer, $instruction[0]),$instruction[1]); | |
} | } |
</code> | </code> |
| |
=== Методы преобразователя для ссылок === | === Методы связи с рендерером === |
| |
Ключевыми методами преобразователя для обработки различного рода ссылок являются: | Ключевые методы Renderer для обработки различных типов ссылок: |
| |
* ''%%function camelcaselink($link) {} // $link вида «SomePage"%%'' | * ''function [[xref>camelcaselink($link)]] %%{} // $link like "SomePage"%%'' |
* Возможно, это будет проигнорировано проверкой на спам-адрес --- маловероятно, что кто-нибудь подобным образом поставить ссылку вне сайта. | *Вероятно, это можно проигнорировать для проверки на спам — никто не должен иметь возможности ссылаться на сторонние сайты с таким синтаксисом. |
* ''%%function internallink($link, $title = NULL) {} // $link вида «[[syntax]]"%%'' | * ''function [[xref>internallink($link, $title = null)]]%% {} // $link like "[[syntax]]"%%'' |
* Хотя ''$link'' сама по себе является внутренней, ''$title'' может быть недоступным изображением, так что требуется проверка. | *Хотя ''$link'' сам по себе является внутренним, ''$title'' может быть изображением, которое находится вне сайта, поэтому необходимо проверить |
* ''%%function externallink($link, $title = NULL) {}%%'' | * ''function [[xref>externallink($link, $title = null)]] {}'' |
* И ''$link'', и ''$title'' (изображение) требуют проверки. | *Оба изображения ''$link'' и ''$title'' (изображения) нуждаются в проверке |
* ''%%function interwikilink($link, $title = NULL, $wikiName, $wikiUri) {}%%'' | * ''function [[xref>interwikilink($link, $title = null, $wikiName, $wikiUri)]] {}'' |
* ''$title'' требует проверки для изображений. | *Необходимость ''$title'' проверки изображений |
* ''%%function filelink($link, $title = NULL) {}%%'' | * ''function [[xref>filelink($link, $title = null)]] {}'' |
* Технически, только годные ''%%file://%%'' URL будут совпадать, но всё равно лучше проверить плюс проверка ''$title'', которое может быть недоступным изображением. | *Технически должны совпадать только действительные ''%%file://%%'' URL-адреса, но, вероятно, лучше все равно проверить, плюс ''$title'' может быть стороннее изображение |
* ''%%function windowssharelink($link, $title = NULL) {}%%'' | * ''function [[xref>windowssharelink($link, $title = null)]] {}'' |
* Требуется проверить только годные адреса доступа к общим ресурсам Windows, но всё равно лучше проверить плюс проверка ''$title'', которое может быть недоступным изображением. | *Должен соответствовать только допустимым URL-адресам общих ресурсов Windows, но в любом случае проверять наличие ''$title'' изображений |
* ''%%function email($address, $title = NULL) {}%%'' | * ''function [[xref>emaillink($address, $title = null)]] {}'' |
* ''$title'' может быть изображением. Проверять ли адрес электропочты? | *''$title'' может быть изображение. Проверить почту тоже?$titleможет быть изображение. Проверить почту тоже? |
* ''%%function internalmedialink ($src,$title=NULL,$align=NULL,$width=NULL,$height=NULL,$cache=NULL) {}%%'' | * ''function [[xref>internalmedialink($src, $title = null, $align = null, $width = null, $height = null, $cache = null)]] {}'' |
* Здесь проверка не требуется --- следует только поставить ссылки на локальные изображения. ''$title'' сам по себе не может быть изображением. | *Это не требует проверки — должно ссылаться только на локальные изображения. ''$title'' само по себе не может быть изображением |
* ''%%function externalmedialink($src,$title=NULL,$align=NULL,$width=NULL,$height=NULL,$cache=NULL) {}%%'' | * ''function [[xref>externalmedialink($src, $title = null, $align = null, $width = null, $height = null, $cache = null)]] {}'' |
* ''$src'' требует проверки. | *''$src'' нуждается в проверке |
| |
Особое внимание следует уделит методам, принимающим в качестве параметра ''%%$title%%'', который представляет видимый текст ссылки, например: | Особого внимания требуют методы, которые принимаютe ''%%$title%%'' аргумент, представляющий видимый текст ссылки, например; |
| |
<code html> | <code html> |
<a href="http://www.example.com/">This is the title</a> | <a href="https://www.example.com">This is the title</a> |
</code> | </code> |
| |
Аргумент ''%%$title%%'' может принимать три возможных типа значений: | Аргумент ''%%$title%%'' может иметь три возможных типа значений; |
| |
- ''NULL'': у документа вики нет заголовка; | - ''null'': в вики-документе заголовок не указан. |
- строка: в качестве заголовка использована простая тестовая строка; | - string: в качестве заголовка использовалась простая текстовая строка |
- массив (хэш): в качестве заголовка использовано изображение. | - array (hash): в качестве заголовка использовано изображение. |
| |
Если ''%%$title%%'' является массивом, он будет содержать ассоциированные значения, описывающие изображение: | Если это ''%%$title%%'' массив, он будет содержать ассоциативные значения, описывающие изображение; |
| |
<code php> | <code php> |
$title = array( | $title = [ |
// Может быть 'internalmedia' (локальное изображение) или 'externalmedia' (внешнее изображение) | // Может быть 'internalmedia' (локальное изображение) или 'externalmedia' (внешнее изображение) |
'type'=>'internalmedia', | 'type' => 'internalmedia', |
| |
// URL изображения (может быть вики-URL или http://static.example.com/img.png) | // URL-адрес изображения (может быть URL-адресом wiki или https://static.example.com/img.png) |
'src'=>'wiki:php-powered.png', | 'src' => 'wiki:php-powered.png', |
| |
// Для альтернативного атрибута - a string or NULL | // Для атрибута alt - строка или null |
'title'=>'Powered by PHP', | 'title' => 'Powered by PHP', |
| |
// 'left', 'right', 'center' или NULL | // 'left', 'right', 'center' или null |
'align'=>'right', | 'align' => 'right', |
| |
// Ширина в пикселях или NULL | // Ширина в пикселях или null |
'width'=> 50, | 'width' => 50, |
| |
// Высота в пикселях или NULL | // Высота в пикселях или null |
'height'=>75, | 'height' => 75, |
| |
// Следует ли кэшировать изображение (для внешних изображений)? | // Кэшировать ли изображение (для внешних изображений) |
'cache'=>FALSE, | 'cache' => false, |
); | ]; |
</code> | </code> |
| |