понедельник, 18 апреля 2011 г.

Управление подсветкой синтаксиса в Sandcastle Help File Builder

На самом деле, лично я качественной подсветке синтаксиса всегда уделяю огромное внимание. Скажем, для этого блога, я потратил часов 5 (в течение нескольких дней), чтобы найти подходящую утилиту, которая бы подсвечивала код в точности также, как это делает Visual Studio.

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

Дак вот, именно поэтому довольно куцая подсветка кода в Sandcastle Help File Builder (далее SHFB), меня не слишком обрадовала (про Sandcastle я совсем недавно писал, это утилита для генерации документации в MSDN-стиле).

И вот, когда засел писать документацию в SHFB уже в третий раз (кстати, для SharePoint Fluent Ribbon), мне все-таки удалось эту проблему победить! Теперь моя документация выглядит всем на зависть и на заглядение:

И между прочим, сделать это совсем несложно. В этой статье я расскажу, как.

Настройки Code Block Component

Оказывается, для подключения подсветки в SHFB, нужно перейти в свойства проекта, найти в самой верхней группе настроек (Build) свойство ComponentConfigurations, и нажать на кнопку с троеточием.

Появится вот такое окошко:


Если жмякнуть на Code Block Component, внизу можно прочитать, что это такое:
This build component is used to search for <code> tags within reference XML comments and conceptual content topics and colorize the code within them. It can also include code from an external file or a region within the file.
Отлично! Теперь добавим его с помощью кнопки Add, и затем нажмем Configure, чтобы задать для него настройки.

Появится очередное окошко, на этот раз намного более интересное:


Я сразу же включил line numbers и collapsible #regions, а также выставил Default Tab Size в 4. Уже лучше, уже намного интереснее. Но, пока еще не идеально. Потому что названия классов не подсвечиваются.

Подсветка названий классов - это вообще больное место для многих Syntax Highlighter'ов. К примеру, на StackOverflow классы подсвечиваются, очевидно, то ли по каким-то хитрым правилам, то ли по очень большому словарю - в общем, эта система очень часто дает сбои, и обеспечивает большое число лишних подсветок.

В Ooki.FormatC для подсветки классов нужно прописывать названия этих классов каждый раз, когда хочешь подсветить какой-нибудь фрагмент кода. Это самая качественная подсветка, но в то же время для больших фрагментов - замучаешься прописывать все эти классы...

А чаще всего, названия классов не подсвечиваются вовсе, и тут был именно такой случай. Что мне, конечно же, категорически не нравилось (на самом деле для блога, Buzz'а и для CodePlex'а, я пользуюсь именно Ooki.FormatC, только не онлайн-версией, а немного доработанным оффлайн-приложением).

Но давайте вернемся к нашим баранам. Именно на странице настроек Code Block Component я обнаружил очень интересные настройки - Language syntax configuration file, и XSLT style transformation file. Мне захотелось посмотреть, что же они собой представляют, и что там можно интересного поправить...

Исследуем файлы настроек

Я перешел в каталог {@SHFBFolder}, коим оказался на самом деле "C:\Program Files (x86)\EWSoftware\Sandcastle Help File Builder\", и внимательно исследовал папку Colorizer. Там оказалось сразу несколько интересных файлов:
1. highlight.css, который содержал стили. Т.е. в случае необходимости, с помощью этого файлика всегда можно подогнать цвета, которые не соответствуют вашим представлениям о правильных цветах для подсветки.
2. highlight.xsl, который содержал трансформации. А именно, некие особые тэги превращал в обычные span'ы, к которым прикреплялись соответствующие стили.
3. highlight.xml, который содержал довольно много разной информации, ну в целом можно сказать, что он описывал алгоритм подсветки, причем делал это с помощью интересной системы контекстов.

Мне очень понравилась эта система контекстов, которая использовалась в файлике highlight.xml, поэтому позволю себе остановится на ней немного поподробнее.

Итак, для каждого языка в файлике объявлена секция <contexts>, с указанием Id контекста по умолчанию, и внутри идут уже описания контекстов. Внутри каждого из контекстов, своеобразные правила, которые делятся на несколько типов, в том числе это может быть regex и keyword. Каждое правило вытаскивает некий фрагмент из обрабатываемого текста. И здесь, внимание! Правила эти делают две вещи: во-первых, назначают вытащенному ими фрагменту атрибут для подсветки, а во-вторых, могут переключать контексты (!). Т.е. к примеру, ключевые слова ловиться в контексте комментариев уже не будут.

В общем, имеем дело с описанием неких состояний анализатора подсветок. Конечный автомат, блин :)

Да, чтобы было понятно, о чем я говорю, вот вам пример секции (для питона, поскольку секция поменьше):
  <!-- Python language specification -->
  <language id="python" tabSize="4" name="Python">
   <!-- Code contexts: default (most common) is code. -->
   <contexts default="code">
    <!-- basic source code context -->
    <context id="code" attribute="code">
     <!-- Single line of comment -->
     <regexp id="python-comment" attribute="comment" context="code" expression="(?&lt;=\W|^)(#.*)([\r\n]{1,2}|$)" />
     <!-- " " literals -->
     <regexp id="dqliteral" attribute="literal" context="code" expression="(&quot;&quot;)|(@&quot;(.|[\r\n])*?&quot;|&quot;(.|\\&quot;|\\\r\n)*?((\\\\)+&quot;|[^\\]{1}&quot;))" />
     <!-- ' ' literals -->
     <regexp id="sqliteral" attribute="literal" context="code" expression="(&apos;&apos;)|(&apos;(.|\\&apos;|\\\r\n)*?((\\\\)+&apos;|[^\\]{1}&apos;))" />
     <!-- numbers -->
     <regexp id="number" attribute="number" context="code" expression="0x[0-9a-fA-F]*|(((?&lt;=[^0-9])(\+|-))?)\b[0-9][0-9]*(\.[0-9]+)?" />
     <!-- keyword Python-like -->
     <keyword attribute="keyword" context="code" family="kwpython-keywords" />
    </context>
    <!-- line comment // ... -->
    <context id="linecomment" attribute="cpp-linecomment">
     <!-- finish line of comment end of line -->
     <linecontinue attribute="hidden" context="code" />
    </context>
   </contexts>
  </language>

Очень интересная и гибкая системка. Качество подсветки, судя по тому что я пока видел - на высоте. И насколько я понял, вся подсветка идет исключительно на основе конфига, т.е. можно задать правила для любого гипотетического языка, и оно будет работать.

Вы скажете - да есть куча других хайлайтеров, которые также работают. А вот и нет, не куча! Самый известный хайлайтер для программистов какой? Scintilla. В свое время я очень скурпулезно изучал файлы настроек подсветки Scintilla, и к сожалению, они не обеспечивают полное описание подсветок (т.е. частично подсветки осуществляются кодом, а не на основе конфига).

Кроме того, с Notepad++ могу сказать, тоже нехорошая история. У него конфиги для подсветок вроде бы и универсальные, но вот качество описаний подсветок - хреновое. Т.е. в итоге, в подсветках очень много брака.

Вот такая вот складывается история...

Добавляем список названий классов

Перед внесением изменений, рекомендую сделать копию папки Colorizer, положив её в папку своего, внимание, проекта документации! Т.е. для каждого проекта будем задавать свои правила подсветок.

Да, и еще один совет: сразу же смените в настройках Code Block Component путь к файлам подсветок, а то потом можно забыть и долго пытаться понять, "что же я делаю не так" :)

Первым делом, определимся, что нам нужно. Мы должны подсвечивать некий список слов. Значит, этот список нужно как-то добавить. Например, можно написать regex, содержащий такой список, но это будет слишком бестолково. Подумав, я решил, что нам вполне подойдет механизм, который используется для keyword.

В начале файла highlight.xml можно увидеть описания списков ключевых слов (keywordlist). Например, список может выглядеть так:

  <!-- Common C-like language keywords (C, C++, C#) -->
  <keywordlist id="kwclang-keywords">
   <kw>break</kw>
   <kw>case</kw>
   <kw>char</kw>
   <kw>const</kw>
   <!-- ... -->
  </keywordlist>

Отлично! Давайте создадим собственный подобный список. У меня получилось следующее:

  <keywordlist id="kwcs-types">
   <kw>String</kw>
   <kw>Boolean</kw>
   <kw>DateTime</kw>
   <kw>Guid</kw>
   <kw>IEnumerable</kw>
   <kw>IList</kw>
   <kw>ICollection</kw>
   <kw>IDictionary</kw>
   <kw>List</kw>
   <kw>Dictionary</kw>
   <kw>SPWeb</kw>
   <kw>SPSite</kw>
   <!-- ... -->
  </keywordlist>


Запоминаем Id, присвоенный нами списку ключевых слов, и идем искать секцию контекстов, ответственную за обработку языка C#. Через несколько секунд, обнаружим следующее:

  <!-- C# language specification -->
  <language id="cs" tabSize="4" name="C#">

В этой секции видим всего 3 контекста: code, linecomment и blockcomment. Очевидно, нам нужен первый. Внутри этого контекста, помимо всяких regex'ов, видим также 3 правила, для выцепляния ключевых слов:

     <!-- keyword C-like -->
     <keyword attribute="keyword" context="code" family="kwclang-keywords" />
     <!-- keyword C++-like -->
     <keyword attribute="keyword" context="code" family="kwclangoo-keywords" />
     <!-- keyword C# -->
     <keyword attribute="keyword" context="code" family="kwcs-keywords" />

Легко догадаться, что мы должны добавить сюда еще один блок, подставив в атрибут family значение kwcs-types.

Но если на этом все и закончить, наши типы будут подсвечиваться синим, как ключевики, а это неверно.

Как вы уже догадались, нам нужно будет изменить атрибут attribute, присвоив ему собственное значение. Именно это я и проделал:

     <!-- C# types -->
     <keyword attribute="csharp-type" context="code" family="kwcs-types" />

Отлично, теперь перечисленные нами названия классов будут помечаться атрибутом csharp-type. Дальше я предположил, что оформление для csharp-type можно добавить в файле highlight.xsl.

Недолго думая, добавил туда следующие строки:

<xsl:template match="csharp-type">
<span class="highlight-csharp-type"><xsl:value-of select="text()" disable-output-escaping="yes" /></span>
</xsl:template>

Уфф, почти все. Осталось последнее - поправить css, добавив туда следующую строку:

    .highlight-csharp-type { color: #2B91AF; }

После этого я скомпилировал проект документации, и получил "идеальную" подсветку. Ура! :)

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

Отправить комментарий

Внимание! Реклама и прочий спам будут беспощадно удаляться.

Примечание. Отправлять комментарии могут только участники этого блога.