Предыдущая версия справа и слеваПредыдущая версияСледующая версия | Предыдущая версия |
wiki:devel:parser [2025/01/16 20:53] – [Handler (Обработчик)] vladpolskiy | wiki:devel:parser [2025/01/17 13:53] (текущий) – [Преобразователь] vladpolskiy |
---|
| |
<diagram> | <diagram> |
| AbstractRewriter.php|| | |AAA||AAA{text-align:left;border-color:white}={{fa>folder?}} Handler |
|)|{{fa>file?}} Block.php| | |)|BBB|BBB{text-align:left;border-color:white}={{fa>file?}} AbstractRewriter.php |
|)| CCC|CCC{text-align:left;border-color:white}=Lists.php | |)|CCC|CCC{text-align:left;border-color:white}={{fa>file?}} Block.php |
|)| Nest.php| | |)|DDD|DDD{text-align:left;border-color:white}={{fa>file?}} CallWriter.php |
|)| ReWriterInterface.php| | |)|EEE|EEE{text-align:left;border-color:white}={{fa>file?}} Nest.php |
|)| Table.php| | |)|FFF|FFF{text-align:left;border-color:white}={{fa>file?}} Lists.php |
|)| CallWriter.php| | |)|GGG|GGG{text-align:left;border-color:white}={{fa>file?}} CallWriterInterface.php |
|)| CallWriterInterface.php| | |)|KKK|KKK{text-align:left;border-color:white}={{fa>file?}} Preformatted.php |
|)| Preformatted.php| | |)|LLL|LLL{text-align:left;border-color:white}={{fa>file?}} Quote.php |
|`| 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> | </diagram> |
| |
| Handler — это класс, предоставляющий методы, которые вызывает Lexer, когда он сопоставляет токены. Затем он «тонко настраивает» токены в последовательность инструкций, готовых для Renderer. |
| |
Обработчик --- это класс, реализующий методы, которые вызываются лексическим анализатором, когда тот обнаруживает вхождения. Затем он «тонко преобразует» вхождения в последовательность инструкций, готовых для передачи преобразователю. | Обработчик в целом содержит следующие классы: |
| |
Обработчик как целое состоит из следующих классов: | * [[wiki:devel:parser:doku_handler|Doku_Handler]]: все вызовы из Lexer производятся в этот класс. Для каждого режима, зарегистрированного в Lexer, будет соответствующий метод в Handler |
| |
* ''Doku_Handler'': все вызовы из анализатора адресованы этому классу. Для каждого режима, зарегистрированного анализатором, будет соответствующий ему метод в обработчике. | ''Doku_Handler'' в [[xref>inc:extension:syntaxplugin.php|/dokuwiki/inc/Extension/SyntaxPlugin.php]] |
* ''Doku_Handler_CallWriter'': реализует «прокладку» между массивом инструкций (массив ''Doku_Handler::$calls'') и методами обработчика, //записывающими// эти инструкции. Пока идёт лексический анализ, он будет временно перемещён другими объектами, вроде ''Doku_Handler_List''. | <code php SyntaxPlugin.php > |
* ''Doku_Handler_List'': отвечает за трансформацию перечня вхождений в инструкции, пока идёт лексический разбор. | 6 use Doku_Handler; |
* ''Doku_Handler_Preformatted'': отвечает за трансформацию предварительно отформатированных вхождений (врезки в «Докувики») в инструкции, пока идёт лексический разбор. | 57 * @see Doku_Handler_Block |
* ''Doku_Handler_Quote'': отвечает за трансформацию вхождений цитат (текста, начинающего с одной или более «>») в инструкции, пока идёт лексический разбор. | 75 * @param Doku_Handler $handler The Doku_Handler object |
* ''Doku_Handler_Table'': отвечает за трансформацию вхождений таблиц в инструкции, пока идёт лексический разбор. | 77 * @param Doku_Handler $handler The Doku_Handler object |
* ''Doku_Handler_Section'': отвечает за вставку инстукций секций, основываясь на позиции инструкций заголовков, только когда лексический анализ завершён --- повторяется однократно. | 80 abstract public function handle($match, $state, $pos, Doku_Handler $handler); |
* ''Doku_Handler_Block'': отвечает за вставку инструкций 'p_open' и 'p_close', будучи осведомлённым об инструкциях 'block level' instructions, только когда лексический анализ завершён (т. е. он повторяется однократно посредством, выдавая полный перечень инструкций и вставляет дополнитльеный инструкции). | </code> |
* ''Doku_Handler_Toc'': отвечает за добавление инструкций таблицы содержания в начало последовательности, основываясь на инструкциях заголовка, только когда лексический анализ завершён (т. е. он повторяется однократно посредством, выдавая полный перечень инструкций и вставляет дополнитльеный инструкции). | ''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'' наподобие: | Обработчик должен предоставлять методы, названные в соответствии с режимами, зарегистрированными в лексическом анализаторе (имея в виду %%mapHandler()%% метод лексического анализатора — см. выше). |
| |
| Например, если вы зарегистрировали режим файла с помощью 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> |
| |