Пример трансформации 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 Twitter LinkedIn Telegram Whatsapp

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

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