Отдельные элементы и группировки
Учитывая следующий фрагмент XML:
<Problems>
<Problem>
<File>file1</File>
<Description>desc1</Description>
</Problem>
<Problem>
<File>file1</File>
<Description>desc2</Description>
</Problem>
<Problem>
<File>file2</File>
<Description>desc1</Description>
</Problem>
</Problems>
Мне нужно произвести что-то вроде
<html>
<body>
<h1>file1</h1>
<p>des1</p>
<p>desc2</p>
<h1>file2</h1>
<p>des1</p>
</body>
</html>
Я пытался использовать ключ, как
<xsl:key name="files" match="Problem" use="File"/>
но я не совсем понимаю, как перейти к следующему шагу или если это даже правильный подход.
3 ответа
Вот как я это сделаю, используя метод Мюнхена. Google 'xslt muenchean' для получения дополнительной информации от умных людей. Может быть, умный путь, но я оставлю это другим.
Одно замечание: я не использую заглавные буквы в начале имен элементов XML, например, "Файл", но это зависит от вас.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:key name="files" match="/Problems/Problem/File" use="./text()"/>
<xsl:template match="/">
<html>
<body>
<xsl:apply-templates select="Problems"/>
</body>
</html>
</xsl:template>
<xsl:template match="Problems">
<xsl:for-each select="Problem/File[generate-id(.) = generate-id(key('files', .))]">
<xsl:sort select="."/>
<h1>
<xsl:value-of select="."/>
</h1>
<xsl:apply-templates select="../../Problem[File=current()/text()]"/>
</xsl:for-each>
</xsl:template>
<xsl:template match="Problem">
<p>
<xsl:value-of select="Description/text()"/>
</p>
</xsl:template>
</xsl:stylesheet>
Идея состоит в том, чтобы ввести каждый элемент File, используя его текстовое значение. Затем отображать только значения файла, если они совпадают с ключевым элементом. Чтобы проверить, одинаковы ли они, используйте generate-id. Существует аналогичный подход, когда вы сравниваете первый соответствующий элемент. Я не могу сказать вам, что является более эффективным.
Я тестировал здесь код, используя Marrowsoft Xselerator, мой любимый инструмент xslt, хотя он больше не доступен, аааик. Результат, который я получил:
<html>
<body>
<h1>file1</h1>
<p>desc1</p>
<p>desc2</p>
<h1>file2</h1>
<p>desc1</p>
</body>
</html>
Это использует msxml4.
Я отсортировал вывод по файлу. Я не уверен, что ты этого хотел.
Надеюсь, это поможет.
Это решение немного проще, более эффективно и в то же время более общее, чем представленное Ричардом:
Это преобразование:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- -->
<xsl:key name="kFileByVal" match="File"
use="." />
<!-- -->
<xsl:key name="kDescByFile" match="Description"
use="../File"/>
<!-- -->
<xsl:template match="/*">
<html>
<body>
<xsl:for-each select=
"*/File[generate-id()
=
generate-id(key('kFileByVal',.)[1])]">
<h1><xsl:value-of select="."/></h1>
<xsl:for-each select="key('kDescByFile', .)">
<p><xsl:value-of select="."/></p>
</xsl:for-each>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
применительно к предоставленному документу XML:
<Problems>
<Problem>
<File>file1</File>
<Description>desc1</Description>
</Problem>
<Problem>
<File>file1</File>
<Description>desc2</Description>
</Problem>
<Problem>
<File>file2</File>
<Description>desc1</Description>
</Problem>
</Problems>
Дает желаемый результат:
<html>
<body>
<h1>file1</h1>
<p>desc1</p>
<p>desc2</p>
<h1>file2</h1>
<p>desc1</p>
</body>
</html>
Обратите внимание на простой образец соответствия первого <xsl:key>
и как, используя второй <xsl:key>
мы находим все Description
"элементы, которые являются братьями и сестрами" File
"элемент, имеющий заданное значение.
Мы могли бы использовать больше шаблонов вместо <xsl:for-each>
однако это довольно простой случай, и решение действительно выигрывает от более короткого, более компактного и более удобочитаемого кода.
Также обратите внимание, что в XSLT 2.0 обычно используется <xsl:for-each-group>
инструкция вместо мюнхенского метода.
Это решение XSLT 1.0 также поможет. Немного более кратко, чем другие решения!
<xsl:template match="/">
<html><body>
<xsl:for-each select="//File[not(.=preceding::*)]">
<h1><xsl:value-of select="." /></h1>
<xsl:for-each select="//Problem[File=current()]/Description">
<p><xsl:value-of select="." /></p>
</xsl:for-each>
</xsl:for-each>
</body></html>
</xsl:template>
Результат:
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<h1>file1</h1>
<p>desc1</p>
<p>desc2</p>
<h1>file2</h1>
<p>desc1</p>
</body>
</html>