Предположим, вы пишете блог, форум или систему комментариев. То есть нечто, где пользователи будут писать много текста. И этот текст они хотят форматировать. Писать вручную HTML, как показывает практика, никому не нравится (никому — это кроме нас, жалких 10-100 тысяч гиков). Значит остаются javascript'овые тулбарчики разной степени удобства и/или упрощенный текстовый синтаксис.

Python Markdown

Для своего проекта я выбрал в качестве такого синтаксиса Markdown (который уже хвалил, повторяться не буду), благо для Питона есть его реализация — Python Markdown. Кстати, судя по контактному email'у и возможности писать на него в том числе и по-русски, пишется она при большом участии наших. И пишется, кстати, активно.

Но я тогда не знал, как мне с ним действительно повезло! Возникла у нас идея расширить синтаксис, чтобы люди, пишущие в форум, могли легко делать ссылки друг на друга. То есть написать:

Уважаемый Модератор, а вот [user]tester пишет неприличные 
слова в комментариях!

... и [user]tester превратилось бы в сслыку на профайл tester'а с соответствующей картиночкой (как в ЖЖ).

Оказалось, что в Python 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 написать.

Недостатки

Их я нашел два:

Но как бы то ни было, результатом я доволен! Рекомендую.

Комментарии: 5

  1. Johnny Woo

    [оффтоп]
    Интересно, это у Вас feed невалидный или Omea Reader неправильно показывает? После фразы "[user]someusername и заменял ее на ссылку" три абзаца идут ссылкой на [http://softwaremaniacs.org/blog/feed/atom/..](http://softwaremaniacs.org/blog/feed/atom/..)..

  2. Сергей

    Здравствуйте, Иван.

    Читаю ваш блог с огромным удовольствием, особенно его часть, относящуюся к django.

    Хочу внести предложение: может в рамках небольшого ликбеза напишите статью для Русской Википедии о Django?

  3. Иван Сагалаев

    Похоже, ридер все же виноват. Вот кусок из фида:

    и заменял ее на ссылку <code>&lt;a href=...&gt;</code>.
    

    Все корректно. Яндекс.Лента и Google Reader тоже справились.

  4. Vyacheslav Lukianov

    Спасибо за информацию, разберемся!

  5. Александр

    С удивлением и радостью обнаружил в питоновском markdown поддержку пользовательских атрибутов.

    Однако, радость моя оказалась недолгой - я обнаружил, что для картинок "по ссылке", это не работает, а для просто картинок - работает прекрасно:

    А мне как раз нужен был вариант со ссылками, так как я хочу загружать картинки, для каждой статьи в блоге, а потом вставлять их в текст по slug ссылкам :(

    Что делать? Лезть ручками в markdown.py?

Добавить комментарий