=============Секция обозначается с помощью "украшающей" линии, которая должна быть под заголовком секции. Также, "украшающая" линия может быть дополнительно и над заголовков. Если имеется только нижняя декорация, то количество символов в ней должно совпадать с количеством символов в названии секции, а само название не может начинаться с пробельного символа. Если есть и нижняя и верхняя декорация, то в начале названия можно вставлять пробельные символы, но длина заголовка должна быть меньше длины декорации. Верхняя (если есть) декорация должна совпадать с нижней. Декорация должна состоять из одинаковых символов, разрешены следующие символы: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ . В общем, как-то так :)
Hello world
=============
Hello world
-----------
Hello world
!!!!!!!!!!!
Ядро wiki-parser - это чуть более 200 строк кода, которые обеспечивают конечный автомат, почти полностью управляемый с помощью регулярных выражений. Код несколько запутан, но концептуально это очень простая схема, позволяющая легко описывать разметку, например, подобную разметке dokuwiki. Однако, statemashin.py (из docutils) это значительно более сложная схема, которая допускает значительно большую гибкость, но и требует значительно больших усилий по программированию. И связано это с необходимостью обрабатывать конструкции, подобные описанным Section, а в разметке reStructuredText есть ещё, например, куда более сложные таблицы. Ключевая проблема - невозможность описания подобной разметки на основе регулярных выражений.
Однако, тут следует сделать оговорку, что нельзя составить необходимое регулярное выражение в Perl или Python, но библиотека cl-ppcre поддерживает фантастическую возможность - фильтры.
cl-ppcre имеет два варианта описания регулярных выражений - традиционный с помощью строки в стиле Perl, и вариант описания на основе s-выражений. Фактически, строковое представление всегда сначала парсится именно в форму s-выражений. Синтаксис символьной формы можно узнать с помощью функции ppcre:parse-string, например для тэга :entry в описании strong-элемента (см. предыдущую часть) получается следующее:
CL-USER> (ppcre:parse-string "\\*\\*(?=.*\\*\\*)")Эта возможность используется в ядре wiki-parser для построения "большого" регулярного выражения (оперировать s-выражениями проще, чем строками), также я порой использую эту возможность, когда затрудняюсь описать нужное мне в строковом виде (обычно, строковое представление удобней, но s-форма допускает большую гибкость). А ещё - s-представление даёт возможность указать произвольную функцию, которая будет работать как часть регулярного выражения. Т.е. когда нельзя написать традиционное регулярное выражение для описания синтаксиса, всегда можно указать произвольную функцию, которая будет производить необходимые проверки!. Для обработки секций я написал следующий код:
(:SEQUENCE "**"
(:POSITIVE-LOOKAHEAD (:SEQUENCE (:GREEDY-REPETITION 0 NIL :EVERYTHING) "**")))
(defparameter +section-adornment+Функция regex-section-filter несколько нудновата, но главное это работает:
'(#\! #\" #\# #\$ #\% #\&
#\' #\( #\) #\* #\+ #\,
#\- #\. #\/ #\: #\; #\<
#\= #\> #\? #\@ #\[ #\\
#\] #\^ #\_ #\` #\{ #\|
#\} #\~))
(defun regex-section-filter (pos)
(ignore-errors
(flet ((check-condition (flag)
(unless flag (return-from regex-section-filter nil))))
(let ((adornment nil)
(title nil)
(overline-p nil))
(check-condition (char= #\Newline
(char ppcre::*string* pos)))
(incf pos)
(check-condition (null (char= #\Newline
(char ppcre::*string* pos))))
(when (member (char ppcre::*string* pos)
+section-adornment+)
(let ((pos2 (position (char ppcre::*string* pos)
ppcre::*string*
:start (1+ pos)
:test-not #'char=)))
(check-condition (char= #\Newline
(char ppcre::*string* pos2)))
(setf adornment (subseq ppcre::*string* pos pos2)
overline-p t
pos (+ pos2 1))))
(unless overline-p
(check-condition (let ((sp (ppcre:scan "\\s" title)))
(or (null sp)
(> sp 0)))))
(let ((pos2 (position #\Newline
ppcre::*string*
:start pos
:test #'char=)))
(setf title
(subseq ppcre::*string* pos pos2))
(setf pos
(+ pos2 1)))
(let ((pos2 (position #\Newline
ppcre::*string*
:start pos
:test #'char=)))
(cond
(adornment (check-condition (string= adornment
(subseq ppcre::*string* pos pos2))))
(t (check-condition (> pos2 pos))
(setf adornment
(subseq ppcre::*string* pos pos2))
(check-condition (member (char adornment 0)
+section-adornment+))
(check-condition (null (find (char adornment 0)
adornment
:test-not #'char=)))))
(setf pos pos2))
(check-condition (if overline-p
(<= (length title)
(length adornment))
(= (length title)
(length adornment))))
pos))))
(define-mode section (30 :sections)
(:special (:filter regex-section-filter))
(:post-handler (item)
(let* ((lines (cdr (ppcre:split "\\n" (second item))))
(decoration (car (last lines)))
(overline-p (= (length lines) 3))
(title (if overline-p
(second lines)
(first lines))))
(list 'section
(trim-whitespaces title)
(char decoration 0)
overline-p))))
CL-USER> (wiki-parser:parse :re-structured-textЗдесь распарсенный элемент WIKI-PARSER.RE-STRUCTURED-TEXT:SECTION имеет название, символ декорации и признак того, что имеется верхняя декорация.
"**strong**
=============
Hello world
=============
*em*")
(WIKI-PARSER.RE-STRUCTURED-TEXT:TOPLEVEL
(WIKI-PARSER.RE-STRUCTURED-TEXT:STRONG "strong"))
(WIKI-PARSER.RE-STRUCTURED-TEXT:SECTION "Hello world" #\= T)
(WIKI-PARSER.RE-STRUCTURED-TEXT:EMPHASIS "em")))
Таким образом, при сохранении предельной простоты для описания тривиальных элементов имеется возможность и для обработки весьма сложной разметки (а код для обработки подобных элементов будет достаточно сложен при любом подходе). Эта схема кажется мне наиболее простой, как в понимании, так и в кодировании и тех, что я видел для обработки подобных разметок.
должно быть labels вместо flet?
ОтветитьУдалить> должно быть labels вместо flet?
ОтветитьУдалитьНет, зачем? В том плане, что с labels бы тоже работало, но check-condition очень проста функция, она не рекурсивная и не вызывает других.