DokuWiki

It's better when it's simple

Инструменты пользователя

Инструменты сайта


ru:devel:parser

Различия

Показаны различия между двумя версиями страницы.

Ссылка на это сравнение

Предыдущая версия справа и слеваПредыдущая версия
Следующая версия
Предыдущая версия
ru:devel:parser [2015-10-23 12:50] – [Обсуждение] 178.213.240.13ru:devel:parser [2016-07-27 23:05] (текущий) – [Обработчик] 93.74.81.132
Строка 1: Строка 1:
-====== Парсер «ДокуВики» ======+====== Парсер «Докувики» ======
  
-В этом документе излагаются детали функционирования парсера «[[ru:dokuwiki|ДокуВики]]», которые могут понадобиться разработчикам для модификации поведения парсера или получения контроля над выходным потоком документа.+В этом документе излагаются детали функционирования парсера «[[ru:dokuwiki|Докувики]]», которые могут понадобиться разработчикам для модификации поведения парсера или получения контроля над выходным потоком документа.
  
 ===== Обзор ===== ===== Обзор =====
  
-Парсер разбивает процесс трансформации исходного документа «ДокуВики» в финальный выходной документ (обычно XHTML) на дискретные стадии. Каждая стадия представлена одним или несколькими PHP-классами.+Парсер разбивает процесс трансформации исходного документа «Докувики» в финальный выходной документ (обычно XHTML) на дискретные стадии. Каждая стадия представлена одним или несколькими PHP-классами.
  
 В общем рассмотрении этими элементами являются; В общем рассмотрении этими элементами являются;
  
-  - Лексический анализатор((__L__exer относится к классу ''Doku_Lexer'' и содержится в файле ''inc/parser/lexer.php''.)): сканирует((Сканирование --- чтение строки PHP от начала до конца.)) исходный документ «ДокуВики» и выводит последовательность «вхождений»((Термин «вхождение» в этом документе относится к совпадению регулярного выраждения, полученного анализатором, и соответствующему вызову метода обработчиком.)), соответствующих синтаксической структуре документа.+  - Лексический анализатор((__L__exer относится к классу ''Doku_Lexer'' и содержится в файле ''inc/parser/lexer.php''.)): сканирует((Сканирование --- чтение строки PHP от начала до конца.)) исходный документ «Докувики» и выводит последовательность «вхождений»((Термин «вхождение» в этом документе относится к совпадению регулярного выраждения, полученного анализатором, и соответствующему вызову метода обработчиком.)), соответствующих синтаксической структуре документа.
   - Обработчик((__H__andler относится к классу ''Doku_Handler'' и содержится в файле ''inc/parser/handler.php''.)): получает вхождения от анализатора и преобразует их в последовательность «инструкций»((Последовательность инструкций содержится в массиве ''$calls'', который является атрибутом обработчика. Предназначен для использования с [[phpfn>call_user_func_array]].)). Он описывает, как должет быть сформирован выходной документ, от начала до конца.   - Обработчик((__H__andler относится к классу ''Doku_Handler'' и содержится в файле ''inc/parser/handler.php''.)): получает вхождения от анализатора и преобразует их в последовательность «инструкций»((Последовательность инструкций содержится в массиве ''$calls'', который является атрибутом обработчика. Предназначен для использования с [[phpfn>call_user_func_array]].)). Он описывает, как должет быть сформирован выходной документ, от начала до конца.
-  - Собственно парсер((__P__arser относится к классу ''Doku_Parser'' и содержится в файле ''inc/parser/parser.php''.)): «связывает» анализатор с обработчиком, предоставляя синтаксические правила «ДокуВики», а также точку доступа к системе (метод ''Parser::parse()'')+  - Собственно парсер((__P__arser относится к классу ''Doku_Parser'' и содержится в файле ''inc/parser/parser.php''.)): «связывает» анализатор с обработчиком, предоставляя синтаксические правила «Докувики», а также точку доступа к системе (метод ''Parser::parse()'')
   - Преобразователь((__R__enderer (:!: от «to render» //в значении// превращать, преобразовывать) относится к абстрактному (implemented) классу ''Doku_Renderer'' - см. ''inc/parser/renderer.php'' и ''inc/parser/xhtml.php''.)): принимает инструкции от обработчика и «отрисовывает» готовый к выводу документ (например, в виде XHTML).   - Преобразователь((__R__enderer (:!: от «to render» //в значении// превращать, преобразовывать) относится к абстрактному (implemented) классу ''Doku_Renderer'' - см. ''inc/parser/renderer.php'' и ''inc/parser/xhtml.php''.)): принимает инструкции от обработчика и «отрисовывает» готовый к выводу документ (например, в виде XHTML).
  
Строка 62: Строка 62:
 === Необходимость в состояниях === === Необходимость в состояниях ===
  
-Синтаксис вики, используемый в «ДокуВики», содержит разметку, «внутри» которой применяются только определённые синтаксические правила. Самый очевидный пример --- тэг %%<code/>%% , внутри которого синтаксис вики не будет распознаваться анализатором. Для других синтаксических конструкций, таких как списки или таблицы, следует позволять использовать //некоторую// разметку, но не всю, например, в списка можно использовать ссылки, но не таблицы.+Синтаксис вики, используемый в «Докувики», содержит разметку, «внутри» которой применяются только определённые синтаксические правила. Самый очевидный пример --- тэг %%<code/>%% , внутри которого синтаксис вики не будет распознаваться анализатором. Для других синтаксических конструкций, таких как списки или таблицы, следует позволять использовать //некоторую// разметку, но не всю, например, в списка можно использовать ссылки, но не таблицы.
  
 Анализатор обеспечивает «осведомлённость о состояниях», позволяющую применять корректные синтаксические правила в зависимости от текущий позиции (контекста) в сканируемом тексте. Если он видит открывающий тэг %%<code>%%, он переключается в другое состояние, в пределах которого другие синтаксические правила не применяются (т. е. что-либо, что выглядит как синтаксис вики должно восприниматься как "простой" текст), до тех пор, пока не найдёт закрывающий тэг %%</code>%%. Анализатор обеспечивает «осведомлённость о состояниях», позволяющую применять корректные синтаксические правила в зависимости от текущий позиции (контекста) в сканируемом тексте. Если он видит открывающий тэг %%<code>%%, он переключается в другое состояние, в пределах которого другие синтаксические правила не применяются (т. е. что-либо, что выглядит как синтаксис вики должно восприниматься как "простой" текст), до тех пор, пока не найдёт закрывающий тэг %%</code>%%.
Строка 112: Строка 112:
 Используется, чтобы реагировать на дополнительные «вхождения» внутри существующего режима (без переходов). Он принимает паттерн и наименование режима, внутри которого должен использоваться. Используется, чтобы реагировать на дополнительные «вхождения» внутри существующего режима (без переходов). Он принимает паттерн и наименование режима, внутри которого должен использоваться.
  
-Это наиболее наглядно видно из разбора парсером синтаксиса списков. Синтаксис списков выглядит в «ДокуВики» следующим образом;+Это наиболее наглядно видно из разбора парсером синтаксиса списков. Синтаксис списков выглядит в «Докувики» следующим образом;
  
 <code> <code>
Строка 145: Строка 145:
 == mapHandler == == mapHandler ==
  
-Позволяет особому режиму быть прикреплённым к методу с разными наименованиями в обработчике. Это может быть полезным, когда различные синтаксические конструкции следует обрабатывать таким образом, как конструкции «ДокуВики», отключающие другие синтаксические конструкции в особенном текстовом блоке:+Позволяет особому режиму быть прикреплённым к методу с разными наименованиями в обработчике. Это может быть полезным, когда различные синтаксические конструкции следует обрабатывать таким образом, как конструкции «Докувики», отключающие другие синтаксические конструкции в особенном текстовом блоке:
  
 <code php> <code php>
Строка 165: Строка 165:
 === Синтаксические ошибки и состояния === === Синтаксические ошибки и состояния ===
  
-Для предотвращение «плохо форматируемой» (особенно при пропуске закрывающих тэгов) разметки, приводящей к тому, что анализатор входит в состояние (режим), который он никогда не покинет, может быть полезным использование паттерна просмотра вперёд для проверки наличия закрывающей разметки((Смысл «плохо форматируемый» не применим к парсеру «ДокуВики» --- он разработан так, чтобы предотвращать случаи, когда пользователь забывает добавить закрывающий тэг некоторой разметки, полностью игнорируя эту разметку.)). Например:+Для предотвращение «плохо форматируемой» (особенно при пропуске закрывающих тэгов) разметки, приводящей к тому, что анализатор входит в состояние (режим), который он никогда не покинет, может быть полезным использование паттерна просмотра вперёд для проверки наличия закрывающей разметки((Смысл «плохо форматируемый» не применим к парсеру «Докувики» --- он разработан так, чтобы предотвращать случаи, когда пользователь забывает добавить закрывающий тэг некоторой разметки, полностью игнорируя эту разметку.)). Например:
  
 <code php> <code php>
Строка 187: Строка 187:
   * ''Doku_Handler_CallWriter'': реализует «прокладку» между массивом инструкций (массив ''Doku_Handler::$calls'') и методами обработчика, //записывающими// эти инструкции. Пока идёт лексический анализ, он будет временно перемещён другими объектами, вроде ''Doku_Handler_List''.   * ''Doku_Handler_CallWriter'': реализует «прокладку» между массивом инструкций (массив ''Doku_Handler::$calls'') и методами обработчика, //записывающими// эти инструкции. Пока идёт лексический анализ, он будет временно перемещён другими объектами, вроде ''Doku_Handler_List''.
   * ''Doku_Handler_List'': отвечает за трансформацию перечня вхождений в инструкции, пока идёт лексический разбор.   * ''Doku_Handler_List'': отвечает за трансформацию перечня вхождений в инструкции, пока идёт лексический разбор.
-  * ''Doku_Handler_Preformatted'': отвечает за трансформацию предварительно отформатированных вхождений (врезки в «ДокуВики») в инструкции, пока идёт лексический разбор.+  * ''Doku_Handler_Preformatted'': отвечает за трансформацию предварительно отформатированных вхождений (врезки в «Докувики») в инструкции, пока идёт лексический разбор.
   * ''Doku_Handler_Quote'': отвечает за трансформацию вхождений цитат (текста, начинающего с одной или более «>») в инструкции, пока идёт лексический разбор.   * ''Doku_Handler_Quote'': отвечает за трансформацию вхождений цитат (текста, начинающего с одной или более «>») в инструкции, пока идёт лексический разбор.
   * ''Doku_Handler_Table'': отвечает за трансформацию вхождений таблиц в инструкции, пока идёт лексический разбор.   * ''Doku_Handler_Table'': отвечает за трансформацию вхождений таблиц в инструкции, пока идёт лексический разбор.
Строка 221: Строка 221:
 </code> </code>
  
-**Замечание:** метод обработчика //обязан// вернуть «TRUE» или анализатор будет немедленно остановлен. Подобное поведение может быть полезным, когда встречаются другие проблемы обработки, но в парсере «ДокуВики» все методы обработчика //всегда// возвращают «TRUE».+**Замечание:** метод обработчика //обязан// вернуть «TRUE» или анализатор будет немедленно остановлен. Подобное поведение может быть полезным, когда встречаются другие проблемы обработки, но в парсере «Докувики» все методы обработчика //всегда// возвращают «TRUE».
  
 Аргументы, реализумые методом обработчика; Аргументы, реализумые методом обработчика;
Строка 330: Строка 330:
   -''%%listitem_open:%%''   -''%%listitem_open:%%''
   -''%%cdata: « This is the opening list item"%%''   -''%%cdata: « This is the opening list item"%%''
-  -''%%listitem_open:%%''+  -''%%listitem_close:%%''
   -''%%listitem_open:%%''   -''%%listitem_open:%%''
   -''%%cdata: « This is the second list item"%%''   -''%%cdata: « This is the second list item"%%''
-  -''%%listitem_open:%%''+  -''%%listitem_close:%%''
   -''%%listitem_open:%%''   -''%%listitem_open:%%''
   -''%%cdata: « This is the last list item"%%''   -''%%cdata: « This is the last list item"%%''
-  -''%%listitem_open:%%''+  -''%%listitem_close:%%''
   -''%%list_close:%%''   -''%%list_close:%%''
   -''%%p_open:%%''   -''%%p_open:%%''
Строка 346: Строка 346:
 ==== Парсер ==== ==== Парсер ====
  
-Парсер играет роль переднего рубежа для внешнего кода и устанавливает для лексического анализатора паттерны и режимы, описывающие синтаксис «ДокуВики».+Парсер играет роль переднего рубежа для внешнего кода и устанавливает для лексического анализатора паттерны и режимы, описывающие синтаксис «Докувики».
  
 Использование парсера в общем случае выглядит следущим образом: Использование парсера в общем случае выглядит следущим образом:
Строка 869: Строка 869:
 **Замечание:** метод ''parse'' парсера разбивает исходный вики текст на предыдущий и последующий символы, чтобы гарантировать корректный выход анализатора из состояний, так что вам требуется вычесть единицу из индекса байта, чтобы получить корректную позицию оригинального исходного вики-текста. Также парсер нормализует строки под стиль Unix’а (т. е. все''%%\r\n%%'' становятся ''%%\n%%''), так что документ, который видит анализатор, может быть меньше, чем тот, который вы в действительности загрузили. **Замечание:** метод ''parse'' парсера разбивает исходный вики текст на предыдущий и последующий символы, чтобы гарантировать корректный выход анализатора из состояний, так что вам требуется вычесть единицу из индекса байта, чтобы получить корректную позицию оригинального исходного вики-текста. Также парсер нормализует строки под стиль Unix’а (т. е. все''%%\r\n%%'' становятся ''%%\n%%''), так что документ, который видит анализатор, может быть меньше, чем тот, который вы в действительности загрузили.
  
-Пример массив инструкций страницы с описанием [[ru:wiki:syntax|синтаксиса]] --- [devel:parser:sample_instructions]].+Пример массив инструкций страницы с описанием [[wiki:syntax|синтаксиса]] --- [[devel:parser:sample_instructions]].
  
 ==== Преобразователь ==== ==== Преобразователь ====
Строка 903: Строка 903:
 Он используется для документирования преобразователя, хотя также может быть расширен, если вы захотите написать преобразователь, который лишь перехватывает определённые вызовы. Он используется для документирования преобразователя, хотя также может быть расширен, если вы захотите написать преобразователь, который лишь перехватывает определённые вызовы.
  
-Основной принцип того, как инструкции, возвращаемые парсером, используются преобразователем, близок по смыслу к [[wp>SAX XML API]] --- инструкции являются перечнем имён функций / методов и их аргуменов. Каждая инструкция может быть вызвана через преобразователь (т. е. реализуемые им методы являются [[wp>Callback_(computer_science)|обратными]]). В отличие от «SAX API», где доступно совсем немного, достаточно общих, обратно вызываемых методов (например, tag_start, tag_end, cdata и т. д.), преобразователь определяет более точную API, где методы обычно соответствуют один-к-одному действию по генерации выходных данных.+Основной принцип того, как инструкции, возвращаемые парсером, используются преобразователем, близок по смыслу к [[wp>ru:SAX|SAX XML API]] --- инструкции являются перечнем имён функций / методов и их аргуменов. Каждая инструкция может быть вызвана через преобразователь (т. е. реализуемые им методы являются [[wp>ru:Callback_(программирование)|обратными]]). В отличие от «SAX API», где доступно совсем немного, достаточно общих, обратно вызываемых методов (например, tag_start, tag_end, cdata и т. д.), преобразователь определяет более точную API, где методы обычно соответствуют один-к-одному действию по генерации выходных данных.
  
 Во фрагменте преобразователя, показанном выше методы ''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>%%''.
Строка 994: Строка 994:
 ==== Основной вызов ==== ==== Основной вызов ====
  
-Чтобы вызвать парсер со //всеми// режимами, и обработать синтаксис документа «ДокуВики»:+Чтобы вызвать парсер со //всеми// режимами, и обработать синтаксис документа «Докувики»:
  
 <code php> <code php>
Строка 1059: Строка 1059:
 $Renderer = & new Doku_Renderer_XHTML(); $Renderer = & new Doku_Renderer_XHTML();
  
-# Здесь загрузите в преобразователь данные типа смайлов+# Здесь загрузите в преобразователь данные (например, типа смайлов)
  
 // Проходимся по всем инструкциям // Проходимся по всем инструкциям
Строка 1086: Строка 1086:
 $Parser->addMode('header',new Doku_Parser_Mode_Header()); $Parser->addMode('header',new Doku_Parser_Mode_Header());
  
-// Загружаем режимы, которые могут содержать разметку, которая может быть принята за заголовок+// Загружаем режимы, которые могут содержать разметку, 
 +// которая может быть принята за заголовок
 $Parser->addMode('listblock',new Doku_Parser_Mode_ListBlock()); $Parser->addMode('listblock',new Doku_Parser_Mode_ListBlock());
 $Parser->addMode('preformatted',new Doku_Parser_Mode_Preformatted());  $Parser->addMode('preformatted',new Doku_Parser_Mode_Preformatted()); 
Строка 1110: Строка 1111:
 $instructions = $Parser->parse($doc); $instructions = $Parser->parse($doc);
  
-// Используем эти переменные, чтобы узнать, находимся ли мы внутри необходимого фрагмента+// Используем эти переменные, чтобы узнать, 
 +// находимся ли мы внутри необходимого фрагмента
 $inSection = FALSE; $inSection = FALSE;
 $startPos = 0; $startPos = 0;
Строка 1148: Строка 1150:
 ==== Управление входными файлами с данными в шаблонах ==== ==== Управление входными файлами с данными в шаблонах ====
  
-«ДокуВики» хранит части некоторых шаблонов во внешних файлах (например, смайлы). Поскольку парсинг и вывод документа являются отдельными стадиями, обрабатываемыми различными компонентами, при использовании данных также требуется дифференцированный подход.+«Докувики» хранит части некоторых шаблонов во внешних файлах (например, смайлы). Поскольку парсинг и вывод документа являются отдельными стадиями, обрабатываемыми различными компонентами, при использовании данных также требуется дифференцированный подход.
  
 Каждый подходящий режим принимает простой список элементов, который он собирает в список шаблонов для регистрации в анализаторе. Каждый подходящий режим принимает простой список элементов, который он собирает в список шаблонов для регистрации в анализаторе.
Строка 1225: Строка 1227:
 </code> </code>
  
-**Замечание:** проверка строк, которые группируются в блоки, обрабатывается другим способом, описанным ниже.+**Замечание:** проверка ссылок, которые необходимо блокировать, обрабатывается другим способом, описанным ниже.
  
 ==== Проверка ссылок на спам ==== ==== Проверка ссылок на спам ====
Строка 1407: Строка 1409:
 ==== Добавление синтаксической конструкции ==== ==== Добавление синтаксической конструкции ====
  
-**Предупреждение:** приведённый ниже код ещё не протестирован --- это только пример.+**Предупреждение:** приведённый ниже код ещё не испытан --- это только пример.
  
 Простая задача по модификации парсера: этот пример будет добавлять тэг-«закладку», который может быть использован для создания якоря в документе для создания ссылки на него. Простая задача по модификации парсера: этот пример будет добавлять тэг-«закладку», который может быть использован для создания якоря в документе для создания ссылки на него.
Строка 1471: Строка 1473:
     // ...     // ...
          
-    // $match - строка, которая сравнивается анализатором с регулярным выражением для закладок+    // $match - строка, которая сравнивается анализатором 
 +    //          с регулярным выражением для закладок
     // $state идентифицирует тип совпадения (см. выше)     // $state идентифицирует тип совпадения (см. выше)
     // $pos - индекс байта первого символа совпадения в исходном документе     // $pos - индекс байта первого символа совпадения в исходном документе
Строка 1493: Строка 1496:
                     $this->__addCall('bookmark', array($name), $pos);                     $this->__addCall('bookmark', array($name), $pos);
                                  
-                // Если у закладки нет годного имени, пропускаем имени +                // Если у закладки нет годного имени, 
-                // through unmodified as plain text (cdata)+                //  пропускаем не меняя как cdata
                 } else {                 } else {
                                  
Строка 1748: Строка 1751:
 Этот синтаксис позволяет искать страницы вики и находить вопросы, которые предстоит решить, выделяя их в документе бросающимся в глаза стилем. Этот синтаксис позволяет искать страницы вики и находить вопросы, которые предстоит решить, выделяя их в документе бросающимся в глаза стилем.
  
-Особенностью данного синтаксиса является то, что он должен отображаться в отдельном блоке документа (например, внутри ''<div/>'', так что он с помощью CSS он может «плавать»). Это требует модификации класса ''Doku_Handler_Block'', который пробегает по всем инструкциям, после того, как обработчиком найдены все вхождения, и заботиться о добавлении тэгов ''<p/>''.+Особенностью данного синтаксиса является то, что он должен отображаться в отдельном блоке документа (например, внутри ''<div/>'', так что он с помощью CSS может «плавать»). Это требует модификации класса ''Doku_Handler_Block'', который пробегает по всем инструкциям, после того, как обработчиком найдены все вхождения, и заботиться о добавлении тэгов ''<p/>''.
  
 Режим парсера для этого синтаксиса может быть таким: Режим парсера для этого синтаксиса может быть таким:
Строка 1993: Строка 1996:
 [[http://ru.wikipedia.org/wiki/Модульное_тестирование|Тесты программных единиц]] обеспечивают использование «[[http://www.lastcraft.com/simple_test.php|Simple Test for PHP]]». «Simple Test» является отличным инструментом для тестирования единиц php-кода. Особенно выделяются блестящая документация (см. [[http://simpletest.sourceforge.net/|simpletest.sourceforge.net]] и [[http://www.lastcraft.com/simple_test.php|www.lastcraft.com/simple_test.php]]) и хорошо продуманый код, обеспечивающий «прозрачное» решение многих вопросов (вроде перехвата ошибок PHP и сообщения о них в результатах тестирования). [[http://ru.wikipedia.org/wiki/Модульное_тестирование|Тесты программных единиц]] обеспечивают использование «[[http://www.lastcraft.com/simple_test.php|Simple Test for PHP]]». «Simple Test» является отличным инструментом для тестирования единиц php-кода. Особенно выделяются блестящая документация (см. [[http://simpletest.sourceforge.net/|simpletest.sourceforge.net]] и [[http://www.lastcraft.com/simple_test.php|www.lastcraft.com/simple_test.php]]) и хорошо продуманый код, обеспечивающий «прозрачное» решение многих вопросов (вроде перехвата ошибок PHP и сообщения о них в результатах тестирования).
  
-Для парсера «ДокуВики» тесты проводились по всем внедряемым синтаксическим конструкциям, и я //очень сильно// рекомендую написание новых тестов, если добавляется новый синтаксис.+Для парсера «Докувики» тесты проводились по всем внедряемым синтаксическим конструкциям, и я //очень сильно// рекомендую написание новых тестов, если добавляется новый синтаксис.
  
-Чтобы запустить тесты, вам следует модифицировать файл ''tests/testconfig.php'', указав корректные директории «Simple Test» и «ДокуВики».+Чтобы запустить тесты, вам следует модифицировать файл ''tests/testconfig.php'', указав корректные директории «Simple Test» и «Докувики».
  
 Некоторые заметки и рекомендации: Некоторые заметки и рекомендации:
Строка 2127: Строка 2130:
 После таблицы После таблицы
  
-Без сканирования текста множества раз (некая разновидность «предварительных» операций, которые всталяют «обёртку»), едва ли можно найти простое решение.+Без сканирования текста множества раз (некая разновидность «предварительных» операций, которые вставляют «обёртку»), едва ли можно найти простое решение.
  
 ==== Проблемы списков, таблиц и цитат ==== ==== Проблемы списков, таблиц и цитат ====
Строка 2171: Строка 2174:
 </code> </code>
  
-Если бы поведение было бы таким же, как в оригинальном парсере «ДокуВики», преобразование было бы таким:+Если бы поведение было бы таким же, как в оригинальном парсере «Докувики», преобразование было бы таким:
  
 До заголовка есть До заголовка есть
Строка 2206: Строка 2209:
 ===== Обсуждение ===== ===== Обсуждение =====
  
-Спасибо за перевод! :-) :-*+Спасибо за перевод! :-)
  
ru/devel/parser.1445597442.txt.gz · Последнее изменение: 2015-10-23 12:50 (внешнее изменение)

Если не указано иное, содержимое этой вики предоставляется на условиях следующей лицензии: CC Attribution-Share Alike 4.0 International
CC Attribution-Share Alike 4.0 International Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki