Создание отчетов на основе xsl-трансформаций (xslt)

18 6

XSLT (eXtensible Stylesheet Language Transformations) - язык преобразований xml-документов.

Введение

Задача генерирования отчетности в системе DIRECTUM является одной из наиболее востребованных.

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

Если очень коротко, то xslt-преобразование заключается в трансформации xml-схемы с данными в отчет на основе предварительно подготовленного шаблона. 

Для примера отчета описанного в статье будет выводится список контрагентов с их наименованием, ИНН и адресом. 

Чтобы сформировать отчет понадобятся две составляющие:

- xml-данные для отчета

- xsl-шаблон отчета 

Данные в формате xml

Данные в формате xml можно получить прямым sql-запросом:

select Analit,  -- ИД записи для генерации ссылки 
  NameAn,       -- Наименование контрагента
  EdIzm,        -- ИНН
  Dop2          -- Адрес
from dbo.MBAnalit
where Vid = %s 
  and Sost = 'Д' 
  and XRecStat = '+'
for xml path('org'), type  -- Получить набор строк <org/> в виде xml                       

Описанный выше запрос вернет данные в таком формате:

<org><Analit>101702</Analit><NameAn>Мобил-Авто ООО</NameAn><EdIzm>123456789</EdIzm><Dop2>426000, г. Ижевск, ул. Революционная, 44</Dop2></org>
<org><Analit>148965</Analit><NameAn>ОАО Тринити</NameAn><EdIzm>631000001</EdIzm><Dop2>443000, Самарская обл., г. Самара, ул. Ленина, 11</Dop2></org>
<org><Analit>148966</Analit><NameAn>ООО Дальний восток</NameAn><EdIzm>011101001</EdIzm><Dop2>100006, г. Владивосток, ул. Первая, 1</Dop2></org>

Данные нужно обернуть в тег <orgs></orgs> и сохранить в файл, добавив заголовок:

<?xml version="1.0" encoding="windows-1251"?>
<?xml-stylesheet type="text/xsl" href="file:///С:/Temp/template.xsl" ?>

Вторая строка заголовка xml-файла данных важна, в ней указано, что в файле C:\Temp\template.xsl хранится шаблон отчета в который необходимо передать данные.

В итоге получится такой xml-файл:

<?xml version="1.0" encoding="windows-1251"?>
<?xml-stylesheet type="text/xsl" href="file:///С:/Temp/template.xsl" ?>
<orgs>
<org><Analit>101702</Analit><NameAn>Мобил-Авто ООО</NameAn><EdIzm>123456789</EdIzm><Dop2>426000, г. Ижевск, ул. Революционная, 44</Dop2></org>
<org><Analit>148965</Analit><NameAn>ОАО Тринити</NameAn><EdIzm>631000001</EdIzm><Dop2>443000, Самарская обл., г. Самара, ул. Ленина, 11</Dop2></org>
<org><Analit>148966</Analit><NameAn>ООО Дальний восток</NameAn><EdIzm>011101001</EdIzm><Dop2>100006, г. Владивосток, ул. Первая, 1</Dop2></org>
</orgs>

Если открыть полученный xml-файл, браузер будет использовать указанный в заголовке шаблон и подставит данные в него.

xsl-шаблон

Далее необходимо создать шаблон, имя файла которого указано в заголовке xml-файла. xsl-шаблон отчета представляет собой обыкновенную html-страницу с особым заголовком:

<?xml version="1.0" encoding="windows-1251"?>
<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<style>
.main{
  font-family:"Segoe UI", Arial;
  font-size:12pt;
  background-color:#fafafa
}
</style>

<body class="main">
<h3>Список контрагентов</h3>
 
<!-- Сюда вставить данные -->

</body>
</html>

В тэге <html> указаны версия и ссылка пространства имен xsl-преобразования. В остальном шаблон - это обыкновенная html-страница.

Теперь нужно сотворить немного магии и указать какие именно данные из xml и в каком виде вставить в шаблон отчета.  

Для работы с данными в xsl-шаблоне используются специальные теги.


Цикл for-each и правила выбора

Чтобы перебрать все узлы из xml-файла данных нужно использовать конструкцию:

<xsl:for-each select="orgs/org">
</xsl:for-each>

Она позволит перебрать все дерево данных. Для того, чтобы выбрать только определенные данные из дерева используется правило выбора select="orgs/org". Правила описаны на языке запросов XPath. В текущем примере будет последовательно отобрана каждый узел <org> из <orgs>.


Сортировка

Если необходимо, чтобы записи отображались не в порядке следования в xml-файле, а сортировались, то нужно указать это в шаблоне:

 <xsl:sort select="NameAn" order="ascending"/>

Теперь записи будут перебираться предварительно отсортированными по наименованию.


Отображение значения

Но просто перебрать не достаточно, нужно вывести в отчет нужные данные, для этого существует конструкция:

<xsl:value-of select="NameAn"/>

Конструкция отобразит значение тэга <NameAn> (наименование контрагента).


Гиперссылки

Чтобы сделать из наименования контрагента ссылку, по которой можно будет открыть запись справочника, нужно немного модифицировать код:

  <a target="_blank">
    <xsl:attribute name="href">http://directum/reference.asp?sys=DIRECTUM&amp;compcode=ОРГ&amp;id=<xsl:value-of select="Analit"/></xsl:attribute>
    <xsl:value-of select="NameAn"/>
  </a>


Сase-оператор и фильтрация

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

    <xsl:choose>
      <xsl:when test="EdIzm != ''">
        <span>ИНН:<xsl:value-of select="EdIzm"/>. </span>
      </xsl:when>
      <xsl:otherwise>
        <span class="red">ИНН не указан. </span>
      </xsl:otherwise>
    </xsl:choose>  

В конструкции choose с помощью фильтра test проводится проверка содержимого тэга <EdIzm> (ИНН) на пустое значение (<xsl:when test="EdIzm !='' ">). Если содержимое не пусто, то выводиться значение. В других случаях выводится сообщение "ИНН не указан".


Еще один фильтр

С помощью фильтра возможно реализовать еще одну "рюшечку", которая будет помечать контрагентов с ИНН начинающихся например с кода региона "18". Выглядеть это будет так:

    <xsl:if test="starts-with(EdIzm,'18')">
      <span class="red">&#9829; </span>
    </xsl:if>

Конструкция для всех записей ИНН которых начинается с "18" дополнительно выведет значок .

В конечном итоге получится такой код шаблона:

<?xml version="1.0" encoding="windows-1251"?>
<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<style>
.main{
  font-family:"Segoe UI", Arial;
  font-size:12pt;
  background-color:#fafafa
}
.nameRow{
  background-color:white;
  color:gray;
  padding:4px
}
.infoRow{
  margin-left:20px;
  margin-bottom:1em;
  font-size:10pt
}
.fontBold{
  font-weight:bold  
}
.fontItalic{
  font-style:italic
}
.red{
  color:red;  
}
</style>

<body class="main">
<h3>Список контрагентов</h3>

<xsl:for-each select="orgs/org">
  <xsl:sort select="NameAn" order="ascending"/>

  <div class="nameRow">
    <xsl:if test="starts-with(EdIzm,'18')">
      <span class="red">&#9829; </span>
    </xsl:if>
    <span class="fontBold">  
      <a target="_blank">
        <xsl:attribute name="href">http://directum/reference.asp?sys=DIRECTUM&amp;compcode=ОРГ&amp;id=<xsl:value-of select="Analit"/></xsl:attribute>
        <xsl:value-of select="NameAn"/>
      </a>
    </span>
  </div>

  <div class="infoRow">
    <p>
    <xsl:choose>
      <xsl:when test="EdIzm != ''">
        <span>ИНН:<xsl:value-of select="EdIzm"/>. </span>
      </xsl:when>
      <xsl:otherwise>
        <span class="red">ИНН не указан. </span>
      </xsl:otherwise>
    </xsl:choose>    
    <span>Адрес: <xsl:value-of select="Dop2"/>.</span>
    </p>
  </div>
</xsl:for-each>

</body>
</html>


Результат

А если открыть xml-файл в браузере, то получится такой отчет:

Вывод

В статье описаны только несколько базовых возможностей xsl, такие как фильтры, сортировка, цикл, case-конструкция. Кроме них существуют еще интересные возможности. Надеюсь, статья подтолкнет изучить и их. Возможно после этого построение отчетов станет более интересным.

А если к отчету подключить такой мощный инструмент как javascript, то описанный отчет легко можно сделать интерактивным. Например добавить элемент, позволяющий проводить какую-либо фильтрацию. Или отображать графики по данным отчета.

Какие есть плюсы при реализации отчетов описанным способом:

 - Требуется разработка относительно небольшого количества прикладных вычислений;

 - Разработка шаблона наглядна и также относительно нетрудоёмка.

Всё описанное в статье легко интегрируется в аналитический отчет, ссылка на который приведена ниже.
Пример xsl отчета

18
Авторизуйтесь, чтобы оценить материал.
5
Роман Деменков

Собственно отображение задач в Директум выполняется как раз таким образом.

Опять же трансформацию можно производить программно:

xml = Object.WorkflowParams.ValueByName('TaskTreeXML').Value

XSLFile = ReadFile('C:\DIRECTUM\TaskScheme.xsl')

xmldom = CreateObject('Msxml2.DOMDocument.6.0')
xmldom.validateOnParse = true
xmldom.async = false
xmldom.loadXML(xml)

xsltdom = CreateObject("Msxml2.DOMDocument.6.0")
xsltdom.validateOnParse = true
xsltdom.async = false
xsltdom.loadXML(XSLFile) 

output = xmldom.transformNode(xsltdom)

FileNameHTML = GetTempFolder() & Object.ID & '.html'
if FileExists(FileNameHTML)
  DeleteFile(FileNameHTML)
endif
WriteFile(FileNameHTML;;'' & CR & output)

 

Александр Чугунов

Не увидел в статье ответа на вопрос зачем нам выгружать данные сначала в xml-файл, а потом еще и писать xslt (который еще и изучить надо), если можно сразу выгрузить в формате html (по сути это тот же xml).
Аргументируйте, пожалуйста, и приведите примеры, применимые к отчетам Directum.
Обычно разделение данных и представления делают когда надо одни и те же данные надо по-разному отображать (отображать в нескольких представлениях динамически формируемые данные или если данные хранятся где-то и отображение может со временем меняться).
С отображением схем задач понятно почему надо использовать xslt - у нас данные уже есть, их надо только отобразить. Я не встречал никогда чтобы данные еще где-то хранились в xml и их надо было только отобразить.
К недостаткам представленного подхода еще можно отнести то, что xsl-файл еще надо где-то размещать, чтобы отчет можно было переслать по почте, например.

Алексей Тутаев

Александр, статья писалась для разработчиков, которые еще не слышали (или слышали, но краем уха) про xslt. И призвана подтолкнуть их к изучению этой темы. 
По поводу того "зачем нам выгружать данные сначала в xml-файл" - можно и не выгружать, а реализовать по-другому, как например выше в комментарии описал Роман. 
"а потом еще и писать xslt (который еще и изучить надо)" - именно для этого статья и писалась, чтобы начать изучать xslt.
Про "сразу выгрузить в формате html" - чтобы это сделать, все равно нужно вычисления писать, которые данные "обернут" в тэги html-разметки. И такой путь не соответсвует парадигме MVC. Если отчетик небольшой и известно, что никаких изменений в нем не предвидится, то, возможно, отступить от этой парадигмы не страшно.
Но если предполагается, что периодически необходимо вносить доработки в отчет, то поддержка такого отчета станет трудоемкой задачей.
К примеру сейчас сущетсвует достаточно много государственных сервисов, которые выдают информацию в виде xml. Эти данные можно сразу отображать в удобно перевариваемом виде, в нужной форме. 
А если случится так, что форма видоизменится и/или в xml добавятся новые (или удалятся старые) реквизиты, то достаточно подкорректировать xsl-шаблон, чтобы форма отчета осталась актуальной не проводя при этом доработок вычислений.
Что касается размещения xsl-файла. То размещать его можно, к примеру, в аналитическом отчете. А чтобы отправить отчет сформированный по xsl-шаблону сам шаблон уже не нужен, можно отправить html-страницу построенную на основе шаблона.

Александр Чугунов

Алексей,
Если у нас уже есть xml и нам надо его отобразить пользователю, то я согласен что надо использовать xslt.
Не надо показывать незнающим разработчикам что надо сначала выгружать sql-запросом в xml, учить xsl и писать преобразование. Думаю, нужна статья, показывающая, что в большинстве случаев html можно и надо формировать прямо на sql. Отображение sql -> html частая задача, xml->html редкая. Изучать что-то новое (при этом сомнительно удобное) и делать на нем все подряд отчеты не стоит. Потому что тем, кому достанется такое наследие, тоже придется учить xslt...
Разделить данные и представление можно в рамках одного sql-запроса. Так что выгружать сначала в xml, а потом его преобразовывать в HTML xslt нет особого смысла, ведь промежуточный xml нигде использоваться не будет. Вот как выглядит sql-запрос для такого же результата:

-- Данные
with sourceData(ID,Name,INN,Address) as (
select Analit,  -- ИД записи для генерации ссылки 
  NameAn,       -- Наименование контрагента
  EdIzm,        -- ИНН
  Dop2          -- Адрес
from dbo.MBAnalit
where Vid = (select Vid from MBVidAn where Kod = 'ОРГ')  
  and Sost = 'Д' 
  and XRecStat = '+')

-- Представление данных
select cast((
select 'nameRow' as [div/@class]
  , case when INN like '18%' then 'redHeart' end as [div/span/@class] 
  , '' as [div/text()]
  , 'fontBold' as [div/span/@class]
  , '_blank' as [div/span/a/@target]
  , 'http://directum.ru/' + cast(ID as varchar) as [div/span/a/@href]
  , Name as [div/span/a]
  , '' as [text()]
  , 'infoRow' as [div/@class]
  , case when INN is null then 'red' end as [div/p/span/@class] 
  , 'ИНН: ' + isnull(INN, 'ИНН не указан.') as [div/p/span] 
  , '' as [div/p/text()]
  , 'Адрес:' + Name as [div/p/span] 
from sourceData
for xml path('')
) as varchar(max))

Это я всё к тому, что статья в каком-то неправильном ключе подана. Сам по себе материал полезен, но логичнее было бы сразу указать, что такой подход стоит использовать если уже есть xml или он для чего-то нужен, а не как промежуточное звено. А лучше сначала написать статью про формирование html-отчетов с помощью for xml, про эту штуку думаю тоже мало народу знает, хотя она ну очень удобная. SQL знают все, учить почти ничего нового не надо.

Алексей Тутаев

Александр, 
Выше я уже написал, что статья для того, чтобы подтолкнуть разработчиков к изучению xsl. И приведенный пример не является идеальным ни с технической, ни с точки зрения бизнес-процессов. Это просто демо-пример с помощью которого описаны базовые xsl-конструкции. 
Отвечу на некоторые Ваши утверждения, которые кажутся мне спорными:
"Отображение sql -> html частая задача, xml->html редкая" - очень спорное утверждение. Слишком много ньюансов для решения различных задач и в каждой задаче луший вариант необходимо выбирать. Универсального нет.
"лучше сначала написать статью про формирование html-отчетов с помощью for xml" - думаю, тема тоже интересная, но эта статья именно про xsl.
"SQL знают все, учить почти ничего нового не надо" - тоже спорное утверждение. 
"Разделить данные и представление можно в рамках одного sql-запроса" - можно. Но далеко не всегда нужно. Все-таки основная задача SQL-сервера не в этом. Особенно это касается крупных заказчиков, у которых SQL-сервер загружен "основной" работой. И, опять же, цель статьи была - рассказать именно про xsl.
И по результатам переписки, думаю Вам есть что рассказать по тематике отображения данных базы данных в виде html-страницы. Если бы такая статья(и) появились на клабе, то было бы очень здорово. Сам бы с удовольствием почитал.

Александр Чугунов

Алексей, давайте не уходить в сторону от основного вопроса, он звучит так: зачем мне делать SQL->XML->[xslt]->HTML, если можно SQL->HTML.
1) Если у нас уже есть XML (в таком формате хранятся данные или в таком формате откуда-то приходит), то я согласен, что хорошим вариантом отображения является использование XSLT. Собственно статья об этом (а не о том, что ВООБЩЕ отчеты хорошо и удобно строить используя xslt). И это надо указать в статье, и не приводить смущающих примеров (с выгрузкой в XML с SQL) и сравнивать с rtf. Подавляющее большинство отчетов в Directum строится на основе данных из SQL-таблиц, а не XML.
2) Аргумент про то, что это не задача SQL - он смешной. SQL это делать умеет, и делает это быстро, дешево и удобно. По сравнению с "лишней" нагрузкой, которую создает работа с ОМ и говнокод, работа с XML - просто семечки. А еще напомню про существование приложений (я лично работал с WebTutor), для которых XML - чуть ли не основной тип хранимых данных и все выборки и преобразования XML производятся в БД. Ну и писать такое и в примере делать SQL->XML как-то нелогично.
3) Чтобы использовать Ваш подход, надо знать и SQL->XML и XSL. Мой же вариант проще, достаточно знать SQL->XML(HTML). Я не услышал ни одного аргумента в пользу SQL->XML->[xslt]->HTML. Сначала Вы говорите, что мой подход "не соответсвует парадигме MVC", а когда я пишу что данные и представление можно разделить и в рамках одного SQL-запроса и XML в вашем подходе ИСПОЛЬЗУЕТСЯ ТОЛЬКО КАК ПРОМЕЖУТОЧНОЕ ЗВЕНО (а не как модель), Вы пишете что разделять модель и представление "далеко не всегда нужно".

Вы можете придумать конкретный пример, когда нам гораздо удобнее и разрабатывать и поддерживать веб-отчет используя SQL->XML->[xslt]->HTML, а не SQL->HTML? Я лично изучал xsl, пытался пользоваться. Но за 5 лет реально что-то делал SQL->XML->[xslt]->HTML 1 раз и то потом переделал на SQL->HTML потому что xsl было излишеством, которое, к тому же, мало кто знал. Я видел веб-контролы в канцелярии на xslt и мое мнение - они слишком сложны для такой простой задачи. SQL->HTML я использовал раз 50 наверно уже (как для простых задач, так и для сложных 1, 2 etc). И не потому что не знаю xsl, а потому что SQL->HTML проще и удобнее. Возможно мне не попадались подходящие задачи, но такой вот у меня опыт. А Вы сколько раз делали SQL->XML->[xslt]->HTML и SQL->HTML?

Авторизуйтесь, чтобы написать комментарий