Пример трансформации XSL с apply-templates, call-template, for-each, if и переменными

3 минуты на чтение

В предприятии, для которого я работаю, документы печатаются в формате PDF через XSL-FO, примерно как в моём недавнем примере. Мне нужно было дополнить шаблон XSL и вывести определённую информацию для каждой позиции заказа от клиента, нашего заказа у оптовой торговли и выставленного счёта. Так как я не был силён в Formating Objects, я сначал накидал шаблон в HTML, примерно такой, как представленный в рамках этого поста, и позже перенёс его в FO.

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

HTML-таблица после преобразования

Скриншот: HTML-таблица после преобразования

XML-файл с данными, который я получал из экпорта из Apex (Shared Components -> Report Queries), состоял из двух информационных блоков, в первом из которых (ROWSET1) были данные о позициях, во втором (ROWSET2) данные о подробностях. Связаны эти данные были полем позиция (POSITION), котрое присутствует в обоих блоках. Каждая ряд данных из блока позиции может иметь одну или несколько соответствующих рядов в подробностях или не иметь их вовсе, каждый ряд данных в подробностях относится к одной из позиций.

Код XML-файла

<DOCUMENT>
	<DATA>
		<ROWSET1>
			<ROWSET1_ROW>
				<POSITION>1</POSITION>
				<NAME>Наименование товара 1</NAME>
				<DESCRIPTION>Описание товара 1</DESCRIPTION>
				<AMOUNT>3</AMOUNT>
				<PRICE>100.00</PRICE>
				<SUM>300.00</SUM>
			</ROWSET1_ROW>
			<ROWSET1_ROW>
				<POSITION>2</POSITION>
				<NAME>Наименование товара 2</NAME>
				<DESCRIPTION>Описание товара 2</DESCRIPTION>
				<AMOUNT>2</AMOUNT>
				<PRICE>300.00</PRICE>
				<SUM>600.00</SUM>
			</ROWSET1_ROW>
			<ROWSET1_ROW>
				<POSITION>3</POSITION>
				<NAME>Наименование товара 3</NAME>
				<DESCRIPTION>Описание товара 3</DESCRIPTION>
				<AMOUNT>1</AMOUNT>
				<PRICE>50.00</PRICE>
				<SUM>50.00</SUM>
			</ROWSET1_ROW>
		</ROWSET1>
		<ROWSET2>
			<ROWSET2_ROW>
				<POSITION>1</POSITION>
				<SERIAL>111111</SERIAL>
			</ROWSET2_ROW>
			<ROWSET2_ROW>
				<POSITION>1</POSITION>
				<SERIAL>222222</SERIAL>
			</ROWSET2_ROW>
			<ROWSET2_ROW>
				<POSITION>1</POSITION>
				<SERIAL>333333</SERIAL>
			</ROWSET2_ROW>
			<ROWSET2_ROW>
				<POSITION>3</POSITION>
				<SERIAL>111111</SERIAL>
			</ROWSET2_ROW>
		</ROWSET2>
	</DATA>
</DOCUMENT>

Мой основной XSL-шаблон выдался достаточно простым. Все данные отображаются в HTML-документе, который содержит таблицу. Таблица содержит шаблон, который используется (apply-templates) для каждого ряда (ROWSET1_ROW) из блока данных с позициями (ROWSET1).

Код основного шаблона

<xsl:template match="DOCUMENT/DATA">
	<html>
		<body>
			<table border="1">
				<xsl:apply-templates select="ROWSET1/ROWSET1_ROW"/>
			</table>
		</body>
	</html>
</xsl:template>

Для каждого ряда данных в ROWSET1 выводится ряд HTML-таблицы (элемент tr — table row), в котором выводится позиция, наименование товара и другие данные, о которых я писал выше. Рядом ниже следует описание товара. Обратите внимание на атрибутcolspan="100%", это значит, что клетка таблицы (элемент td) будет размером до конца ряда — первая клетка в ряду пуста, значит клетка с описанием будет размером в четыре клетки. Ниже запоминаем текущую позицию в переменной this_position и проверяем в if-блоке, есть ли к этой позиции подробности в ROWSET2. Если есть, то открываем шаблон (call-template) с названием details и передаём текущую позицию через параметр (with-param).

Честно сказать, я долго матюкался, так как в XSL нет нормальных переменных, ведь переменную нельзя изменить после объявления. К тому же здесь нет циклов, которые работают, как в других языках программирования. Чтобы узнать, существуют ли подробности, мне пришлось применить функцию count и выражение XPath, в котором я сравниваю текущую позицию с позициями в блоке ROWSET2.

Код шаблона позиций

<xsl:template match="ROWSET1_ROW">
	<tr> 
		<td><xsl:value-of select="POSITION"/></td>
		<td><xsl:value-of select="NAME"/></td>
		<td><xsl:value-of select="AMOUNT"/></td>
		<td><xsl:value-of select="PRICE"/></td>
		<td><xsl:value-of select="SUM"/></td>
	</tr>
	<tr>
		<td></td>
		<td colspan="100%"><xsl:value-of select="DESCRIPTION"/></td>
	</tr>
	<xsl:variable name="this_position" select="POSITION"/>
	<xsl:if test="count(//DOCUMENT/DATA/ROWSET2/ROWSET2_ROW[POSITION=$this_position]) > 0">
		<xsl:call-template name="details">
			<xsl:with-param name="id_position" select="POSITION" />
		</xsl:call-template>
	</xsl:if>
</xsl:template>

В последнем шаблоне я сравниваю переданный параметр с позициями в ROWSET2 и вывожу данные соответствующие параметру (for-each). Кстати, string($id_position) != '' — это своеобразный XSL-вариант выражения != null в Java. Если объект не существует, то каст в строку будет пустой строкой. Я проверяю на всякий случай, была ли передана позиция и в случае, если нет, не вывожу ничего.

Код шаблона подробностей

<xsl:template name="details">	
	<xsl:param name="id_position" />
	<xsl:if test="string($id_position) != ''">
		<tr>
			<td></td>
			<td colspan="100%">
				
				<xsl:variable name="this_count" select="count(//DOCUMENT/DATA/ROWSET2/ROWSET2_ROW[POSITION=$id_position])"/>
				<xsl:text>Подробности (</xsl:text><xsl:value-of select="$this_count"/>)<xsl:text>: </xsl:text>
				<xsl:for-each select="/DOCUMENT/DATA/ROWSET2/ROWSET2_ROW">					
					<xsl:variable name="this_position" select="POSITION"/>
					<xsl:if test="$this_position = $id_position">
						<xsl:value-of select="SERIAL"/>
						<xsl:text>; </xsl:text>
							
					</xsl:if>
				</xsl:for-each>		
			</td>
		</tr>	
	</xsl:if>
</xsl:template>

Вы можете скачать архив с примером, который содержит XSL-файл и XSL-файл. Распаковав архив и открыв файл index.xml браузером, в случае если трансформация прошла успешно, вы увидите не XML с данными, а HTML, как на первом и единственном скриншоте в этом посту.

Понравился пост? Поделись в соцсетях и подписывайся на аккаунты в Twitter и Facebook!
Facebook Vk Ok LinkedIn Telegram Whatsapp

Похожие записи:

Пакеты PL/SQL являются объектами базы данных (схемы), которые объединяют в себе связанные по смыслу подпрограммы (переменные, курсоры…). В этом посту рассмотрим простейший пример объявления пакета, его реализации и использования. Состоят пакеты из спецификаци...
Написал небольшой и несложный тестовый класс для демонстрации работы Apache FOP, который выдаёт PDF-файл с «Hello World!». Сам я немного тормозил при знакомстве с FOP и такой пример мне тогда бы здорово помог. Надеюсь, вам он поможет избежать таких трудностей....
Для одного проекта для учёбы мне нужно было обрабатывать события нажатия кнопок на вебсайте. Простейший пример, как это можно реализовать с помощью JavaScript, а именно библиотеки jQuery, рассмотрим в этом посту. В коде ниже при загрузке страницы к ней прикре...