Группировка Мюнха

При работе с xml/xslt довольно часто возникает потребность в группировке xml-узлов по каким-либо признакам.Например из хаотичного списка городов

[codesyntax lang=»xml»]

<cities>
	<city>Yalta</city>
	<city>Izmail</city>
	<city>Sochi</city>
	<city>Ovidiopol</city>
	<city>Alushta</city>
	<city>Odessa</city>
</cities>

[/codesyntax]

нам надо получить отсортированный по алфавиту и сгрупированный по буквам список

[codesyntax lang=»xml»]

<letter-list>
	<letter name="A">
		<city>Alushta</city>
	</letter>
	<letter name="I">
		<city>Izmail</city>
	</letter>
	<letter name="O">
		<city>Odessa</city>
		<city>Ovidiopol</city>
	</letter>
	<letter name="S">
		<city>Sochi</city>
	</letter>
	<letter name="Y">
		<city>Yalta</city>
	</letter>
</letter-list>

[/codesyntax]

Очень интересное и компактное решение было предложено Стивом Мюнхом (Steve Muench), техническим гуру из Oracle Corporation. Оно основывается на двух фактах:

  1. Для выборки множества узлов по их свойствам можно использовать ключи (xsl:key).
  2. При помощи функции generate-id можно выяснить, является ли узел первым узлом множества в порядке просмотра xml-документа.

С первым пунктом все должно быть понятно, ведь xsl:key как раз и используется для выбора множества узлов по заданному критерию. Второй пункт не является таким уж интуитивно понятным, ведь generate-id используетя для генерации уникальных id для xml-узлов. Вся фишка в том, что если аргументом является множество узлов, то generate-id возвращает уникальный идентификатор первого в порядке просмотра документа узла переданного ей множества! Теперь становится понятным, что для того, чтобы проверить, является ли некий узел первым узлом группы, достаточно сравнить его уникальный идентификатор со значением выражения generate-id($group), где $group — множество узлов этой группы.

С учетом выше сказанного получим:

[codesyntax lang=»xml»]

<xsl:key name="cityLetter" match="city" use="substring(.,1,1)"/>

<xsl:template match="cities">
	<letter-list>
		<xsl:apply-templates select="city[generate-id(.)=generate-id(key('cityLetter',substring(.,1,1)))]"/>
	</letter-list>
</xsl:template>

<xsl:template match="city">
	<letter name="{substring(.,1,1)}">
		<xsl:copy-of select="key('cityLetter',substring(.,1,1))"/>
	</letter>
</xsl:template>

[/codesyntax]

Результатом будет:

[codesyntax lang=»xml»]

<letter-list>
	<letter name="Y">
		<city>Yalta</city>
	</letter>
	<letter name="I">
		<city>Izmail</city>
	</letter>
	<letter name="S">
		<city>Sochi</city>
	</letter>
	<letter name="O">
		<city>Ovidiopol</city>
		<city>Odessa</city>
	</letter>
	<letter name="A">
		<city>Alushta</city>
	</letter>
</letter-list>

[/codesyntax]

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

[codesyntax lang=»xml»]

<xsl:template match="cities">
	<letter-list>
		<xsl:apply-templates select="city[generate-id(.)=generate-id(key('cityLetter',substring(.,1,1)))]">
			<xsl:sort/>
		</xsl:apply-templates>
	</letter-list>
</xsl:template>

<xsl:template match="city">
	<letter name="{substring(.,1,1)}">
		<xsl:apply-templates select="key('cityLetter',substring(.,1,1))" mode="sortable"> <!-- изменили -->
			<xsl:sort/>
		</xsl:apply-templates>
	</letter>
</xsl:template>

<xsl:template match="city" mode="sortable"> <!-- добавили -->
	<xsl:copy-of select="."/>
</xsl:template>

[/codesyntax]

Мы просто отсортировали по алфавиту сначала <letter>, а затем и <city> с помощью xsl:sort (без параметров он использует строку с порядком сортировки по возрастанию). В результате мы добились того, чего и хотели.

Запись опубликована в рубрике XSLT/XPath с метками , , . Добавьте в закладки постоянную ссылку.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *