Синтаксические плагины - это плагины расширения синтаксиса DokuWiki. Чтобы понять, что необходимо для регистрации нового синтаксиса в DokuWiki, вы должны прочитать, как работает Parser.
Для синтаксического плагина example требуется объявить имя класса как syntax_plugin_example который расширяет класс DokuWiki_Syntax_Plugin1). класс необходимо сохранить в файле с названием lib/plugins/example/syntax.php. Для более подробной информации можно обратиться к статье структура файлов плагина.
Класс должен содержать как минимум следующие функции:
getInfo() – возвращает хэш с информацией о плагине (Автор, почта, дата, название, описание, ссылки).getType() – должен возвращать тип синтаксиса (см. ниже).getSort() – возвращает число, по которому определяется в каком порядке должны добавляться состояния, см. также parser, order of adding modes and getSort list.connectTo($mode) – эта функция наследуется от класса Doku_Parser_Mode 2). Это место, где регистрируется регулярные выражения, необходимые для опознания вашего синтаксиса.handle($match, $state, $pos, &$handler) – функция для подготовки совпавшего синтаксиса для использования рендерером.render($mode, &$renderer, $data) – функция для отображения контента.
Следующие дополнительные методы могут быть переопределены при необходимости:
getPType() – определяет как данный синтаксис размещается относительно параграфов 3). Возвращает:normal — (значение по умолчанию, используется, если метод не переопределяется) Плагин может использоваться внутри параграфов,block — Открытые параграфы должны быть закрыты до вывода плагина или stack — Специальный случай. Плагин обертывает другие параграфыgetAllowedTypes() (значение по умолчанию: array()) Должен вернуть массив типов состояний, которые могут быть включены в собственную разметку плагина.accepts($mode) – Эта функция говорит парсеру допускает ли плагин синтаксическое состояние $mode в своей разметке. Поведение по умолчанию заключается в проверке наличия $mode в массиве состояний, хранящемся в унаследованном свойстве allowedModes. При необходимости могут быть определены дополнительные функции. Рекомендуется добавлять спереди символ подчерка к названиям самостоятельно определенных функций для того, чтобы избежать возможных пересечений имен при дальнейшем развитии спецификации плагинов.
Унаследованные свойства
allowedModes — начальное значение - пустой массив, унаследованный от класса Doku_Parser_Mode 4). Содержит список других синтаксических состояний, появление которых допустимо внутри собственного синтаксического состояния плагина (т.е. состояния, которые относятся к любым другим элементам разметки DokuWiki, которые могут быть включены внутрь собственной разметки плагина). Обычно, он автоматически заполняется функцией accepts() используя результаты getAllowedTypes().DokuWiki использует различные синтаксические типы для того, чтобы определить какой синтаксические конструкции могут быть вставлены внутрь другой. Например, вы можете вставить текстовое форматирование внутрь таблицы. Для того, чтобы интегрировать ваш плагин в эту систему, нужно указать какие тип он имеет и какие типы могут вставляться в него. В настоящий момент доступны следующие типы:
| Тип | Используется в … | Описание |
|---|---|---|
| container | listblock, table, quote, hr | Контейнеры - это сложные состояния, которые могут содержать много других состояний, но они не должны использоваться в таблицах/списках (состояние hr нарушает общий принцип), поэтому они отнесены к этому типу |
| baseonly | header | Некоторые состояния допустимы только внутри базового состояния |
| formatting | strong, emphasis, underline, monospace, subscript, superscript, deleted, footnote | состояния для изменения стиля текста (сноски5) также можно рассматривать как стиль) |
| substition6) | 'acronym', 'smiley', 'wordblock', 'entity', 'camelcaselink', 'internallink', 'media', 'externallink', 'linebreak', 'emaillink', 'windowssharelink', 'filelink', 'notoc', 'nocache', 'multiplyentity', 'quotes', 'rss' | Состояния, в которых токен просто меняется на что-то, они не могут содержать в себе другие состояния |
| protected | 'preformatted', 'code', 'file', 'php', 'html' | Состояния, имеющие начальный и конечный токены, но внутри которых не допустимы никакие другие состояния |
| disabled | unformatted | внутри этого состояния разметка wiki не отрабатывается, но переносы строки и пробельные символы не сохраняются |
| paragraphs | eol | используется для отметки границ параграфов |
Для описания того, что каждый из типов значит, и какие другие классы форматирования зарегистрированы в них, читайте комментарии в файле inc/parser/parser.php.
Цель данного руководства - разобрать концепции касающиеся синтаксических плагинов DokuWiki и пройти шаги связанные с написанием вашего собственного плагина.
Для тех, кто с особым нетерпением жаждет начать: возьмите копию плагина syntax plugin skeleton.
For those who are really impatient to get started, grab a copy of the syntax plugin skeleton. Это своего рода костяк - плагин, который выводит ”Hello World!” когда встречает токен ”<TEST>” в статье wiki. Начните наращивать на него мясо.
состояния - modes
handle
handle() вызывается, когда парсер решит что столкнулся в содержимом статьи wiki с куском относящимся синтаксису вашего состояния.$state говорит, какой тип шаблона из приписанных к вашему состоянию сработал. Если это просто обычный текст, то параметр state будет установлен в DOKU_LEXER_UNMATCHEDrender(), потому что выдача метода handle кэшируется. This also means that you shouldn't do any stuff here that mustn't be cached.render
$renderer->doc .= 'content';handle().
Нет никакой гарантии, что метод render() будет вызван в тоже время, что и метод handle(). Инструкции произведенные хендлером кэшируются и могут использоваться рендерером в более позднее время. Единственный надежный способ передать данные от handle() к render() - это использовать возвращаемый методом handle() массив, который передается методу render() в качестве параметра $data.
Состояния (более точно синтаксические состояния) это основа на которой базируется парсер DokuWiki. Каждый отдельный элемент разметки DokuWiki имеет свое синтаксическое состояние. Например, существует состояние strong для работы со strong, состояние superscript для работы с superscript, состояние table для работы таблицами и многие другие.
Когда парсер сталкивается с разметкой, он попадает в соответствующее этой разметкой синтаксическое состояние. Свойства и методы конкретного синтаксического состояния управляют тем как ведет себя парсер пока он в этом состоянии, включая:
Ваш плагин добавит свое синтаксическое состояние к парсеру - это автоматически производиться DokuWiki, когда впервые загружает плагин, назначаемое имя – plugin_+ имя директории плагина (которое также именем класса плагина без префикса ”syntax_”). Затем, когда парсер сталкивается с разметкой, используемой вашим плагином, он (парсер) войдет в это синтаксическое состояния. Пока он находится в этом состоянии, ваш плагин управляет тем, что может делать парсер.
Для упрощения синтаксические состояния, которые ведут себя одинаковым образом были сгруппированы в несколько типов состояний - полный список может быть найден в статье синтаксические плагины.
Каждый тип состояний соотносится с ключом в массиве $PARSER_MODES. Элемент этого массива (соответствующий каждому типу состояний) сам является массивом, который содержит все синтаксические состояния, которые относятся к этому типу. Например, в “чистом” DokuWiki без установленных плагинов элемент массива $PARSER_MODES['formatting'] содержит: 'strong', 'emphasis', 'underline', 'superscript', 'subscript', 'monospace', 'deleted' и 'footnote'.
Когда плагин загружается в парсер, этот плагин запрашивается (через getType()) о том, к какому типу состояний он относится. Затем синтаксические состояния относящиеся к плагину добавляются в соответствующий массив $PARSER_MODES.
Указанный вашим плагином тип состояний определяет, где в статье DokuWiki парсер будет опознавать разметку вашего плагин. Другие синтаксические состояния DokuWiki (и плагины) не будут знать о вашем плагине, но они знают о различных типах состояний.
Если они допускают конкретный тип состояний, он допускают все состояния этого типа, включая любые плагины, которые заявили этот тип состояний.
Выберете тип состояний для вашего плагина сравнивая поведение вашего плагина с поведением стандартных состояний DokuWiki. Выберете тип, к которому относятся наиболее похожие состояния.
Есть другие состояния которые могут возникнуть внутри разметки ваше собственного состояния.
Каждое синтаксическое состояние имеет собственный массив допустимых состояний, который говорит парсеру, какие именно другие синтаксические состояния будут опознаваться во время обработки состояния. То есть, если вы хотите чтобы ваш плагин мог оказаться внутри разметки ”**strong**”, тогда состояние strong должен включить состояние вашего плагина в свой массив allowedModes. И если вы хотите позволить разметке strong включаться внутрь разметки вашего плагина, то ваш плагин должен содержать 'strong' в своем массиве allowModes.
Ваш плагин собирает в массив allowedModes другие синтаксические состояния посредством типа состояний, который он объявляет методом getType().
Ваш плагин сообщает парсеру, какие другие синтаксические состояния он допускает, декларируя их через метод getAllowedTypes().
PType определяет как парсеру работать с html-элементами <p>, когда имеет дело с ваши синтаксическим состоянием.
Обычно, в тот момент когда парсер сталкивается с некоторой разметкой, имеется открытый открытый HTML-тэг параграфа. Парсеру необходимо знать должен ли он закрыть этот тэг перед входом в ваше синтаксическое состояние и затем открыть другой параграф на выходе, в этом случае PType='block', или парсер должен оставить параграф в покое, тогда PType='normal'.
Есть третья возможность PType='stack', которую я пока не понимаю, поэтому не буду о ней писать сейчас (
).
Для тех кто знает CSS, возвращение PType='block' означает html произведенный вашим плагином будет похож на display:block, а возвращение PType='normal' означает html произведенный вашим плагином будет похож на display:inline.
There is one gotcha with — [corrected in DW2006-11-06 version (and preceding release candidates)]
PType='block'. If your plugin allows other syntax modes, the parser will generate </p> & <p> tags when entering and exiting any nested syntax modes. If that causes problems, choose PType='normal' and start the HTML your render method generates with a </p> and finish it with a <p>.
Этот номер используется лексером7) для управления парядком, в котором он проверяет шаблоны синтаксических состояний на сырых данных wiki. Это важно только в том случае, если один и тот же участок данных попадает в шаблоны относящиеся к двум или более состояниям, будет выбран шаблон относящийся к состоянию с наименьшим порядковым номером.
Вы можете использовать это свойство для написания плагина, который заменяет или расширяет “родной” хендлер DokuWiki для той же синтаксической конструкции. Примером является плагин code.
Подробности о существующих порядковых номерах доступны для обоих parser (sort list).
Парсер использует PHP-функции preg8). Детальное объяснения регулярных выражений и их синтаксиса выходит за пределы этого руководства. Существует много хороших источников в интернете.
Полный синтаксис preg не доступен для использования в конструировании шаблонов синтаксических плагинов. Ниже приведен список известных различий:
|” при множественных альтернативах, сделайте их non-captured группами, т.е. ”(?:cat|dog)”(?i), (?-i)
Парсер предоставляет плагину четыре функции для регистрации необходимых шаблонов. Каждая функция относится к шаблонам с разными смыслами.
addSpecialPattern() — это шаблоны, которые используются, когда один шаблон это все, что нужно. В терминах парсера эти шаблоны представляют и вход в синтаксическое состояние плагина, и выход из этого синтаксического состояния, все в одно сравнение. Обычно они используются в плагинах substition.addEntryPattern() — шаблон, который указывает на начало данных, которые должны быть обработаны плагином. Обычно эти шаблоны должны включать в себя заглядывание вперед для проверки существования выходного шаблона. Любой плагин, который регистрирует входной шаблон, также должен зарегистрировать выходной шаблон.addExitPattern() — шаблон, который указывает на конец данных, которые должны быть обработаны плагином. Этот совпадение с этим шаблоном может произойти, только если было найдено совпадение с входным шаблоном.addPattern() — они представляют специальный синтаксис применимый к плагину, который может встретиться между входным и выходным шаблонами. Обычно это нужно только для достаточно сложных структур, например, таблиц и списков.
Один плагин может добавить несколько шаблонов в парсер, включая более чем один шаблон одного типа.
Советы
+? или *? вместо + или *.
Это часть пашего плагина, которая должна совершать всю работу. До того, как DokuWiki выведет статью wiki, он создает список инструкций для рендерера. Метод handle() плагина создают инструкции отображения для собственного синтаксического состояния плагина. В некий более поздний момент они будут интерпретированы методом render() плагина. Список инструкций кэшируется и может быть использован много раз, разумно максимально увеличить объем работы совершаемой один раз этой функцией и максимально уменьшить объем работы совершаемый много раз функцией render().
параметр $match — Текст, который совпадает с шаблоном, или в случае DOKU_LEXER_UNMATCHED непрерывный кусок обычного текста, который не совпал с каким-либо шаблоном.
параметр $state — Тип шаблона, из-за которого запустился вызов handle().
DOKU_LEXER_ENTER — шаблон установлен функцией addEntryPattern()DOKU_LEXER_MATCHED — шаблон установлен функцией addPattern()DOKU_LEXER_EXIT — шаблон установлен функцией addExitPattern()DOKU_LEXER_SPECIAL — шаблон установлен функцией addSpecialPattern()DOKU_LEXER_UNMATCHED — обычный текст встреченный внутри синтаксического состояния плагина, который не совпал ни с одним шаблоном.параметр $pos — позиция первого символа найденного текста
параметр &$handler — Ссылка на объект Doku_Handler.
Часть плагина, которая производит вывод окончательной веб-страницы - или какой-либо другого поддерживаемого формата. Именно здесь плагин добавляет собственный вывод к уже созданным другими частями рендерера путем склейки с свойством doc рендера. Т.е.
$renderer->doc .= "некий вывод плагина...";
В любых сырых данных wiki, которые передаются render(), все спецсимволы должны быть преобразованы в элементы HTML. Вы можете использовать PHP функции htmlspecialchars(), htmlentities() или собственный метод xmlEntities() рендера. Т.е.
$renderer->doc .= $renderer->_xmlEntities($text);
параметр $mode — имя формата состояния финального вывода произведенного рендерером. В настоящее время DokuWiki поддерживает только один формат вывода - XHTML 9)
Новые состояния могут быть представлены в плагинах рендерера. Плагины должны производить вывод только для тех форматов, которые они поддерживают, это значит, что эта функция должна быть структурирована …
if ($mode == 'xhtml') { // supported mode
// code to generate XHTML output from instruction $data
}
параметр $data — Массив содержащий инструкции предварительно подготовленные собственным методом handle() плагина. Эта функция должна интерпретировать инструкции и выдавать соответствующий вывод.
Сырые данные wiki, которые достигли вашего плагина больше никогда не должны обрабатываться. Никакой дальнейшей обработки не производится над выводом после, того как он покидает плагин. Как минимум, плагин должен убедиться, что в выводе все спецсимволы HTML заменены на HTML-последовательности. Так же к извлеченным и используемым внутри данным wiki нужно относится с подозрением. См. также статью безопасность.
Смотрите статьи локализация и структура файлов планина
Смотрите статью configuration.
Смотрите статью plugin file structure
Для того, чтобы облегчить жизнь пользователям wiki, которые проинсталлировали ваш плагин, вам следует добавить кнопку в тулбар редактора.
См. статью плагины действий, sample_action_plugin_2, см. также toolbar.
Ну хорошо, вы решили расширить синтаксис DokuWiki своим собственным плагином. Вам придется разработать каким будет ваша синтаксическая конструкция, и как она будет отображаться в браузере пользователя. Теперь вам необходимо написать сам плагин.
lib/plugins/. Эта директория должна называться также как ваш плагин.syntax.php. В качестве отправной точки, можете использоватсиь skeleton plugin. Скопируйте его в свою директорию.syntax_plugin_<название вашего плагина> 10).getInfo() чтобы, он выдавал информацию о вашем плагине. getType() чтобы, он выдавал тип состояний, к которому относится ваш плагин. getAllowedTypes() для сообщения всех типов состояний, которые ваш плагин может включать внутрь своей собственной синтаксической конструкции. Если ваш плагин не желает позволить каком-либо состоянию включаться в себя, он может быть выкинут.getPType() чтобы, он выдавал PType, который относится к вашему плагин. Если это 'normal', вы можете просто убрать этот метод.getSort() чтобы, он выдавал уникальный номер, проверьте его в списке плагинов.connectTo() чтобы, зарегистрировать шаблон для опознавания вашего синтакиса.postConnect(), если ваш синтаксис имеет второй шаблон, для того, чтобы указать когда парсер должен покинуть ваше синтаксическое состояние.handle() и render().
Когда синтаксическая конструкция этого плагина [NOW] встречается в статье wiki текущая дата и время отображается в формате RFC2822.
'substition'. Мы подставляем временую метку вместо токена [NOW], аналогично смайлам и акронимами. Они также относятся к типу состояний 'substition'.[NOW]. Таким образом, нам не нужен метод getAllowedTypes().normal, это значение по умолчанию, поэтому нам не нужно определять метод getPType().[NOW]. Единственная вещь, с которой нужно быть осторожным, это то, что символы ”[” и ”]” имеют специальное значение в регулярных выражениях, поэтому нам нужно “выключить”11) их, создав шаблон '\[NOW\]'.handler() не должен ничего делать. Нам не нужно заботиться о специальных состояниях или дополнительных параметрах в нашем синтаксисе. Мы просто вернем пустой массив в качестве инструкций для рендерера.render(), это добавить штамп времени к текущей статье wiki — $renderer->doc .= date('r');И вот наш плагин завершен!
<?php /** * Plugin Now: Inserts a timestamp. * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Christopher Smith <chris@jalakai.co.uk> */ // must be run within DokuWiki if(!defined('DOKU_INC')) die(); if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); require_once(DOKU_PLUGIN.'syntax.php'); /** * All DokuWiki plugins to extend the parser/rendering mechanism * need to inherit from this class */ class syntax_plugin_now extends DokuWiki_Syntax_Plugin { function getInfo(){ return array( 'author' => 'me', 'email' => 'me@someplace.com', 'date' => '2005-07-28', 'name' => 'Now Plugin', 'desc' => 'Include the current date and time', 'url' => 'http://www.dokuwiki.org/plugin:tutorial', ); } function getType() { return 'substition'; } function getSort() { return 32; } function connectTo($mode) { $this->Lexer->addSpecialPattern('\[NOW\]',$mode,'plugin_now'); } function handle($match, $state, $pos, &$handler) { return array($match, $state, $pos); } function render($mode, &$renderer, $data) { if($mode == 'xhtml'){ $renderer->doc .= date('r'); return true; } return false; } } ?>
Замечание: из-за способа, которым DokuWiki кэширует страницы, этот плагин будет отображать дату/время для момента, когда был создан кэш страницы. Вам нужно добавить на страницу ~~NOCACHE~~, для того, чтобы отображалось правильное время каждый раз когда запрашивается страница.
Когда встретится синтаксическая конструкция плагина <color somecolour/somebackgroundcolour> встречается в статье wiki, цвет текста сменяется на somecolour, а цвет фона на somebackgroundcolour, и оба остаются такими пока не встретится </color>.
substition, formatting и disabled.normal, это значение по умолчанию, поэтому нам снова не нужен метод getPType().'<color.*>(?=.*?</color>)'. Выходной шаблон аналогично </color>.handle() должен иметь дело с тремя состояниями: совпадающими с входным и выходным шаблонами и “несовпадающим” для промежуточного текста. DOKU_LEXER_ENTER требует некоторой обработки, для извлечения значений цветов текста и фона, они войдут в инструкцию для нашего рендерера.DOKU_LEXER_UNMATCHED не требует какой-либо обработки, но нам придется передать “несовпадающий” текст (в параметре $match) методу render(), поэтому он войдет в инструкцию для нашего рендерера.DOKU_LEXER_EXIT не требует какой-либо обработки и не имеет никаких особых данных, мы просто должны сделать выходную инструкцию для render().render() необходимо иметь дело с теми же тремя сотояниями, что и handle().DOKU_LEXER_ENTER – отрыть тэг span с указанием стилем использующего значения цветов текста и фона.DOKU_LEXER_UNMATCHED – добавить “несовпавший” текст к выходному документу.DOKU_LEXER_EXIT – закрыть тэг span.Опять же, все достаточно очевидно. И вот что мы имеем:
<?php /** * Plugin Color: Sets new colors for text and background. * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Christopher Smith <chris@jalakai.co.uk> */ // must be run within Dokuwiki if(!defined('DOKU_INC')) die(); if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); require_once(DOKU_PLUGIN.'syntax.php'); /** * All DokuWiki plugins to extend the parser/rendering mechanism * need to inherit from this class */ class syntax_plugin_color extends DokuWiki_Syntax_Plugin { /** * return some info */ function getInfo(){ return array( 'author' => 'Christopher Smith', 'email' => 'chris@jalakai.co.uk', 'date' => '2008-02-06', 'name' => 'Color Plugin', 'desc' => 'Changes text colour and background', 'url' => 'http://www.dokuwiki.org/plugin:tutorial', ); } function getType(){ return 'formatting'; } function getAllowedTypes() { return array('formatting', 'substition', 'disabled'); } function getSort(){ return 158; } function connectTo($mode) { $this->Lexer->addEntryPattern('<color.*?>(?=.*?</color>)',$mode,'plugin_color'); } function postConnect() { $this->Lexer->addExitPattern('</color>','plugin_color'); } /** * Handle the match */ function handle($match, $state, $pos, &$handler){ switch ($state) { case DOKU_LEXER_ENTER : list($color, $background) = preg_split("/\//u", substr($match, 6, -1), 2); if ($color = $this->_isValid($color)) $color = "color:$color;"; if ($background = $this->_isValid($background)) $background = "background-color:$background;"; return array($state, array($color, $background)); case DOKU_LEXER_UNMATCHED : return array($state, $match); case DOKU_LEXER_EXIT : return array($state, ''); } return array(); } /** * Create output */ function render($mode, &$renderer, $data) { if($mode == 'xhtml'){ list($state,$match) = $data; switch ($state) { case DOKU_LEXER_ENTER : list($color, $background) = $match; $renderer->doc .= "<span style='$color $background'>"; break; case DOKU_LEXER_UNMATCHED : $renderer->doc .= $renderer->_xmlEntities($match); break; case DOKU_LEXER_EXIT : $renderer->doc .= "</span>"; break; } return true; } return false; } // validate color value $c // this is cut price validation - only to ensure the basic format is correct and there is nothing harmful // three basic formats "colorname", "#fff[fff]", "rgb(255[%],255[%],255[%])" function _isValid($c) { $c = trim($c); $pattern = "/^\s*( ([a-zA-z]+)| #colorname - not verified (\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}))| #colorvalue (rgb\(([0-9]{1,3}%?,){2}[0-9]{1,3}%?\)) #rgb triplet )\s*$/x"; if (preg_match($pattern, $c)) return trim($c); return ""; } } ?>
Замечание: Никаких проверок на корректность названий цветов или значения RGB не производилось.
lib/plugins/syntax.phpDoku_Handler_Blockmetadata, которое ничего не выводит, только собирает метаданные для страницы. Используйте его для вставки значений в массив метаданных