04.05.2012 00:00:27: Переработал дизайн (стало лучше или хуже?), пофиксил несколько неприятных багов.
Если нужен исходник плагина для фиксации навигации при скроллинге оставьте свой контакт.
Небольшое уточнение: весь код проверяю в IE начиная с версии 7 и в Chrome (на данный момент актуальна 18 версия), буду признателен за информацию об ошибках в других браузерах - пишите сообщения на соответствующих страницах.
Скачать:
Есть идеи, замечания?
Просмотров: 19948

Парсер Markdown текста

Что такое Markdown

Если коротко - правила форматирования текста и перевода этого формата в HTML представление. Подробно можно почитать на сайте проекта Markdown (англ.).

Существующие реализации

Есть несколько проектов реализаций работы с markdown и редактирования markdown, в частности Showdown - редактор и парсер markdown на JavaScript (англ.). Обычно реализации опираются на оригинальный исходный код Markdown, котрый написан на перле и имеет несколько моментов, которые лично мне не нравятся:

  • Преобразование с использованием RegExp: исходный текст много раз прогоняется через регулярные выражения и в итоге мы получаем строку, содержащую HTML, т.е. просто работа с текстом.
  • Жестко заданный HTML: мы не можем быстро поменять правила формирования HTML, для этого нам придется переписать парсер.
  • Т.к. идет работа просто с текстом, то для выделения всех ссылок на внешние ресурсы нам нужно делать дополнительный парсинг.
  • Требовательное отношение парсеров к исходному тексту, ошибки при работе со сложным текстом.

Моя реализация

Код написан на JavaScript в инфраструктуре jQuery (из jQuery используется только реализация $.each(...) ) т.к. в дальнейшем планируется разработка редактора.
Реализация состоит из 2-х частей:

  1. Собственно парсер, который строит DOM модель
  2. Генератор HTML для DOM объектов

Такой подход дает множество преимуществ:

  1. Имея объектную модель мы можем оперировать объектами как нам захочется
  2. У нас есть массив ссылок на ресурсы, которые используются в тексте
  3. Мы можем легко заменять генератор HTML и генерить не только HTML
  4. Мы можем построить содержание текста (TOC) и сгенерить HTML для содержания с автоматической генерацией ссылок на разделы текста
  5. Более корректный разбор текста, чем это делает showdown.

Отрицательная сторона: мой парсер работает в 2 раза медленнее, чем showdown.

Дополнение: после некоторых оптимизаций ситуация стала неоднозначной (время - мс)
ПарсерIE7FF3SafariChrome
Showdown64292741
Моя реализация1001092321

Парсер и DOM модель

Основные элемены DOM модели:

  • ROOT - корневой элемент документа, в нем могут находится только P и Section элементы.
  • Section - раздел текста, у него есть ряд свойств: заголовок и уровень. В нем могут находится только P и Section элементы, при этом Section элементы должны иметь более низкий уровень.
  • P - абзац, в нем могут находится элементы: L, QUOTE, PRE, HR, BR, EM, CODE, LINK, MEDIA.
  • L - список, может быть как нумерованным, так и ненумерованным, в нем может находится только элемент LI.
  • LI - элемент списка, в нем может находится только элемент P.
  • QUOTE - цитата, в этом элементе может находится только элемент P.
  • PRE - преформатированный текст, никакие элементы в нем находиться не могут.
  • HR - горизонтальный разделитель, никакие элементы в нем находиться не могут.
  • BR - перевод строки, никакие элементы в нем находиться не могут.
  • EM - выделение текста (обычно жирный или курсив), в нем могут находится элементы: BR, EM, CODE, LINK, MEDIA.
  • CODE - inline вставка преформатированного текста, никакие элементы в нем находиться не могут.
  • LINK - ссылка на ресурс, в нем могут находится элементы: EM, CODE, LINK, MEDIA.
  • MEDIA - отображение ресурса, никакие элементы в нем находиться не могут.
  • URL - ссылка на ресурс и ее параметры

ROOT и URL пренадлежат документу, остальные элементы размещаются внутри ROOT.

Генератор HTML

Генератор состоит из 2-х частей: генератор HTML для всех элементов, кроме MEDIA и генератор отображения элемента MEDIA.

Генератор HTML для всех элементов, кроме MEDIA

для таких элементов нужно реализовать всего пару методов на элемент: генерация кода перед содержимым и генерация кода после содержимого.

Генератор HTML для элементов MEDIA

Для этого элемента его содержимое находится вне текста и тип отображения диктуется ссылкой на содержимое, поэтому для отображения элементов MEDIA идет анализ ссылки того, что нужно отобразить и в зависимости от результатов вызывается соответствующий метод генерации кода.

Используя этот подход мы можем совершенно прозрачно для того, кто пишет текст, генерить HTML код для отображения картинок, swf, роликов с youtube и других подобных сайтов!

Обычно во всех WYSIWYG редакторах для веба используется другой подход: пользователь при форматировании текста должен воспользоваться специальным инструментом, который вставляет только один тип ресурса (даже не тип, а код для него) и очень часто панель такого редактора содержит кучу кнопок для вставки разных ресурсов. Я считаю это неправильным, т.к. совершенно не напрягаясь можно написать код автоматического распознования типа вставляемого ресурса.
На данный момент распознаются:

  • Картинки: jpg, gif, png.
  • Flash ролики
  • YouTube видео

Мои расширения синтаксиса Markdown

Имея объектную модель можно достаточно просто расширять синтаксис. На данный момент я сделал пока только такие расширения:

  1. Можно выделять не только текст, но и абзацы, для этого достаточно в первой строке абзаца вставить признак выделения:
    • !i: - абзац с сообщением
    • !!: - абзац с предупреждением
    • !!!: - абзац с очень важным сообщением
  2. Можно указать выравнивание в абзаце, для этого достаточно в первой строке абзаца вставить метку, аналогичную выделению:
    • !<: - выравнивание по правому краю
    • !+: - выравнивание по центру
    • !>: - выравнивание по левому краю
    Пока выравнивание можно объединить с выделением, но насколько это правильно - вопрос.
  3. У отображения ресурсов можно задавать размер: ![](/img.png 100*200)
    И еще побочный эффект, который я пока решил не удалять: если написать такой текст !</img.png>, то вместо ссылки будет отображена картинка.
  4. Нумерованный списки можно обозначать не только цифрами, а так же символом решетки: #.
  5. Теперь можно задавать подсветку синтаксиса для PRE блоков, для этого используется вовремя подоспевшая подсветка синтаксиса.
    Для указания языка, который нужно подсветить, достаточно в первой строке блока вставить метку, аналогичную той, что ставится у абзаца:
    • !JS: - синтаксис JavaScript
    • !CS: - синтаксис C#
    • !XML: - синтаксис XML
    • !HTML: - синтаксис HTML
    Вообще можно использовать любой синтаксис из $.DmSyntax, если пакет синтаксиса не подключен, то синтаксис выделяться не будет.
  6. По-умолчанию медиа выводится в тексте, в особых случаях можно медиа размещать в отдельном блоке, для этого достаточно написать два восклицательных знака вместо одного: !![Текст под картинкой](/img.png).

Планы по развитию

  1. Возможность задавать подсветку синтаксиса для PRE блоков, примерно как выделение для абзацев, вот и подсветка подоспела...
  2. Реализация надстрочного (^надстрочного^) и подстрочного (~подстрочного~) текста.
  3. Замены в тексте: -- => —, (c) => ©, (r) => ®, (tm) => ™, ... => ….
  4. Дополнительный атрибут отображения ресурсов: в тексте или в отдельном блоке, так же теперь можно задавать выравнивание в абзаце
  5. Сноски
  6. Реализация на C# для использования на сервере
  7. Реализация вставки таблиц - есть мысли как это сделать просто и сердито с поддержкой col-, rowspan и выравниваний в ячейках
  8. Отображение разных типов MEDIA: rutube, и т.д.
  9. Реализация поддержки HTML вставок (хотя это есть зло!)

Пример работы парсера и генератора HTML

Исходный код

Можно менять текст в контроле, при потере фокуса текст будет обработан.

HTML, сгенеренный из DOM модели генератором по-умолчанию:

Нужно учесть, что именно такой вид отображения сделать только для того, что бы показать структуру получившихся объектов, для получения чего-то более удобочитаемого нужно или переписать генераторы HTML или использовать другой CSS.