Предположим, вы пишете блог, форум или систему комментариев. То есть нечто, где пользователи будут писать много текста. И этот текст они хотят форматировать. Писать вручную HTML, как показывает практика, никому не нравится (никому — это кроме нас, жалких 10-100 тысяч гиков). Значит остаются javascript'овые тулбарчики разной степени удобства и/или упрощенный текстовый синтаксис.
Python Markdown
Для своего проекта я выбрал в качестве такого синтаксиса Markdown (который уже хвалил, повторяться не буду), благо для Питона есть его реализация — Python Markdown. Кстати, судя по контактному email'у и возможности писать на него в том числе и по-русски, пишется она при большом участии наших. И пишется, кстати, активно.
Но я тогда не знал, как мне с ним действительно повезло! Возникла у нас идея расширить синтаксис, чтобы люди, пишущие в форум, могли легко делать ссылки друг на друга. То есть написать:
Уважаемый Модератор, а вот [user]tester пишет неприличные
слова в комментариях!
... и [user]tester
превратилось бы в сслыку на профайл tester'а с соответствующей картиночкой (как в ЖЖ).
Оказалось, что в Python Markdown есть куда дописать свои функции, обрабатывающие текст, причем на трех этапах:
- обработать исходный текст прямо перед markdown'ом
- обработать получившееся дерево HTML'а после markdown'а
- обработать отдельные текстовые куски с помощью регулярок
Так вышло, что я попробовал все три подхода, прямо по мере чтения документации.
Сначала написал препроцессор который в исходном тексте искал регуляркой конструкцию [user]someusername
и заменял ее на ссылку <a href=...>
. Это потом вылезло неприятным эффектом: конструкция заменялась на ссылки в том числе и в блоках кода. Хотя, казалось бы, кому придет в голову писать блоки кода в форуме музыкального сервиса? Правильно — мне :-). Я просто пытался привести пример того, как именно вводить эти имена пользователей, и пример взял, и тоже заменился. Нехорошо!
Тогда я написал постпроцессор с очень головоломным алгоритмом (не надо понимать): берется дерево (почти DOM), из него выбираются все текстовые узлы, проверяя, не лежат ли они внутри <code>
, каждый разбивается регуляркой на список из участков обычного текста и моих ссылок, и из этих кусков потом составляется новый список узлов: текстовых и <a href=...>
, причем последние конструируются почтиDOM-функциями, старый узел изымается из дерева, а новые узлы вставляются на его место. Оно работало, но процесс мне не понравился. Это были те 20 минут, которые были потеряны зря.
Но буквально после этого я дочитался до третьей возможности: подсунуть свою функцию для обработки отдельных текстовых кусков. Я задаю регулярку для своих ссылок, и функцию, которая вызывается для всего, что к этой регулярке подходит, которая возвращает узел <a href...>
. Другими словами, это тот же второй вариант, но без ручного парсинга и перелопачивания дерева. Причем замена эта сама уже исключает текст, который не должен парсится.
Если кому-то будет интересен код:
from markdown import Markdown, BasePattern
# Класс-обработчик регулярок
class UserLinksPattern(BasePattern):
def handleMatch(self, match, doc):
a = doc.createElement('a')
a.setAttribute('href', '/users/%s/'%match.group(2))
a.setAttribute('class', 'user')
a.appendChild(doc.createTextNode(match.group(2)))
return a
# Функция замены текста
def markdown(value):
md=Markdown(str(value))
md.inlinePatterns.append(UserLinksPattern(r'\[user\]([A-Za-z0-9_-]+)'))
return md.toString()
Как видно, все довольно просто.
Примечание для джангонавтов: вот эта def markdown
— это фактически рабочий фильтр для шаблона, достаточно перед ней @register.filter
написать.
Недостатки
Их я нашел два:
Документации практически нет. Все описание расширяемости синтаксиса состоит из 4 абзацев и 3 маленьких примеров ("What docs? Read the code, Luke!")
ПочтиDOM-функции на самом деле местами совсем не DOM. Их не только меньше (что было бы вполне нормально), но они и отличаются. Например, вместо
parentNode
используетсяparent
, а уinsertChild
обратный порядок аргументов. Все это отнимает лишнее время для ползания по коду, вместо того, чтобы просто пользоваться тем, что уже известно.Мне кажется, что лучше бы это дерево вообще не притворялось DOM'ом. DOM не самый удобный API, и там для Питона слишком много букв. Лучше было бы что-нибудь вроде ElementTree.
Но как бы то ни было, результатом я доволен! Рекомендую.
Комментарии: 5
[оффтоп]
Интересно, это у Вас feed невалидный или Omea Reader неправильно показывает? После фразы "[user]someusername и заменял ее на ссылку" три абзаца идут ссылкой на
[http://softwaremaniacs.org/blog/feed/atom/..](http://softwaremaniacs.org/blog/feed/atom/..).
.Здравствуйте, Иван.
Читаю ваш блог с огромным удовольствием, особенно его часть, относящуюся к django.
Хочу внести предложение: может в рамках небольшого ликбеза напишите статью для Русской Википедии о Django?
Похоже, ридер все же виноват. Вот кусок из фида:
Все корректно. Яндекс.Лента и Google Reader тоже справились.
Спасибо за информацию, разберемся!
С удивлением и радостью обнаружил в питоновском markdown поддержку пользовательских атрибутов.
Однако, радость моя оказалась недолгой - я обнаружил, что для картинок "по ссылке", это не работает, а для просто картинок - работает прекрасно:
А мне как раз нужен был вариант со ссылками, так как я хочу загружать картинки, для каждой статьи в блоге, а потом вставлять их в текст по slug ссылкам :(
Что делать? Лезть ручками в markdown.py?