Как я предотвращаю дублирующиеся записи в список, и затем идеально, вид тот список? То, что я делаю, когда информация на одном уровне отсутствует, беря информацию от уровня ниже его, к созданию недостающего списка, на уровне выше. В настоящее время я имею XML подобный этому:
<c03 id="ref6488" level="file">
<did>
<unittitle>Clinic Building</unittitle>
<unitdate era="ce" calendar="gregorian">1947</unitdate>
</did>
<c04 id="ref34582" level="file">
<did>
<container label="Box" type="Box">156</container>
<container label="Folder" type="Folder">3</container>
</did>
</c04>
<c04 id="ref6540" level="file">
<did>
<container label="Box" type="Box">156</container>
<unittitle>Contact prints</unittitle>
</did>
</c04>
<c04 id="ref6606" level="file">
<did>
<container label="Box" type="Box">154</container>
<unittitle>Negatives</unittitle>
</did>
</c04>
</c03>
Я затем применяю следующий XSL:
<xsl:template match="c03/did">
<xsl:choose>
<xsl:when test="not(container)">
<did>
<!-- If no c03 container item is found, look in the c04 level for one -->
<xsl:if test="../c04/did/container">
<!-- If a c04 container item is found, use the info to build a c03 version -->
<!-- Skip c03 container item, if still no c04 items found -->
<container label="Box" type="Box">
<!-- Build container list -->
<!-- Test for more than one item, and if so, list them, -->
<!-- separated by commas and a space -->
<xsl:for-each select="../c04/did">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="container"/>
</xsl:for-each>
</container>
</did>
</xsl:when>
<!-- If there is a c03 container item(s), list it normally -->
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Но я получаю "контейнерный" результат
<container label="Box" type="Box">156, 156, 154</container>
когда то, что я хочу,
<container label="Box" type="Box">154, 156</container>
Ниже полный результат, который я пытаюсь получить:
<c03 id="ref6488" level="file">
<did>
<container label="Box" type="Box">154, 156</container>
<unittitle>Clinic Building</unittitle>
<unitdate era="ce" calendar="gregorian">1947</unitdate>
</did>
<c04 id="ref34582" level="file">
<did>
<container label="Box" type="Box">156</container>
<container label="Folder" type="Folder">3</container>
</did>
</c04>
<c04 id="ref6540" level="file">
<did>
<container label="Box" type="Box">156</container>
<unittitle>Contact prints</unittitle>
</did>
</c04>
<c04 id="ref6606" level="file">
<did>
<container label="Box" type="Box">154</container>
<unittitle>Negatives</unittitle>
</did>
</c04>
</c03>
Заранее спасибо за любую справку!
Попробуйте следующий код:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output indent="yes"></xsl:output>
<xsl:template match="node() | @*">
<xsl:copy>
<xsl:apply-templates select="node() | @*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="c03/did">
<xsl:choose>
<xsl:when test="not(container)">
<did>
<!-- If no c03 container item is found, look in the c04 level for one -->
<xsl:if test="../c04/did/container">
<xsl:variable name="foo" select="../c04/did/container[@type='Box']/text()"/>
<!-- If a c04 container item is found, use the info to build a c03 version -->
<!-- Skip c03 container item, if still no c04 items found -->
<container label="Box" type="Box">
<!-- Build container list -->
<!-- Test for more than one item, and if so, list them, -->
<!-- separated by commas and a space -->
<xsl:for-each select="distinct-values($foo)">
<xsl:sort />
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="." />
</xsl:for-each>
</container>
<xsl:apply-templates select="*" />
</xsl:if>
</did>
</xsl:when>
<!-- If there is a c03 container item(s), list it normally -->
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Он выглядит примерно так, как вы хотите:
<?xml version="1.0" encoding="UTF-8"?>
<c03 id="ref6488" level="file">
<did>
<container label="Box" type="Box">154, 156</container>
<unittitle>Clinic Building</unittitle>
<unitdate era="ce" calendar="gregorian">1947</unitdate>
</did>
<c04 id="ref34582" level="file">
<did>
<container label="Box" type="Box">156</container>
<container label="Folder" type="Folder">3</container>
</did>
</c04>
<c04 id="ref6540" level="file">
<did>
<container label="Box" type="Box">156</container>
<unittitle>Contact prints</unittitle>
</did>
</c04>
<c04 id="ref6606" level="file">
<did>
<container label="Box" type="Box">154</container>
<unittitle>Negatives</unittitle>
</did>
</c04>
</c03>
Хитрость заключается в использовании
и разные-значения ()
вместе. См. (IMHO) замечательную книгу Майкла Ки «XSLT 2.0 и XPATH 2.0»
Для этой проблемы нет необходимости в решении XSLT 2.0 .
Вот решение XSLT 1.0, которое более компактно, чем выбранное в настоящее время решение XSLT 2.0 (35 строк против 43 строк):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kBoxContainerByVal"
match="container[@type='Box']" use="."/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="c03/did[not(container)]">
<xsl:copy>
<xsl:variable name="vContDistinctValues" select=
"/*/*/*/container[@type='Box']
[generate-id()
=
generate-id(key('kBoxContainerByVal', .)[1])
]
"/>
<container label="Box" type="Box">
<xsl:for-each select="$vContDistinctValues">
<xsl:sort data-type="number"/>
<xsl:value-of select=
"concat(., substring(', ', 1 + 2*(position() = last())))"/>
</xsl:for-each>
</container>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Когда это преобразование применяется к изначально предоставленному XML-документу, правильный , создается желаемый результат :
<c03 id="ref6488" level="file">
<did>
<container label="Box" type="Box">156, 154</container>
<unittitle>Clinic Building</unittitle>
<unitdate era="ce" calendar="gregorian">1947</unitdate>
</did>
<c04 id="ref34582" level="file">
<did>
<container label="Box" type="Box">156</container>
<container label="Folder" type="Folder">3</container>
</did>
</c04>
<c04 id="ref6540" level="file">
<did>
<container label="Box" type="Box">156</container>
<unittitle>Contact prints</unittitle>
</did>
</c04>
<c04 id="ref6606" level="file">
<did>
<container label="Box" type="Box">154</container>
<unittitle>Negatives</unittitle>
</did>
</c04>
</c03>
Обновление:
Я не заметил требования, чтобы номера контейнеров отображались отсортированными. Теперь решение отражает это.
попробуйте использовать группу ключей в xslt, вот статья о методе Мюнчи, который должен помочь устранить дубликаты. http: //www.jenitennison .com / xslt / grouping / muenchian.html
Настоящее решение XSLT 2.0, также довольно короткое :
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="c03/did[not(container)]">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:variable name="vContDistinctValues" as="xs:integer*">
<xsl:perform-sort select=
"distinct-values(/*/*/*/container[@type='Box']/text()/xs:integer(.))">
<xsl:sort/>
</xsl:perform-sort>
</xsl:variable>
<xsl:if test="$vContDistinctValues">
<container label="Box" type="Box">
<xsl:value-of select="$vContDistinctValues" separator=","/>
</container>
</xsl:if>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Обратите внимание:
Использование типов позволяет избежать необходимости указывать тип данных
в
.
Использование атрибута разделителя
в
Немного более короткая версия XSLT 2.0, сочетающая подходы из других ответов. Обратите внимание, что сортировка выполняется по алфавиту, поэтому, если будут найдены метки «54» и «156», на выходе будет «156, 54». Если требуется числовая сортировка, используйте
вместо
.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="c03/did[not(container)]">
<xsl:variable name="containers"
select="../c04/did/container[@label='Box'][text()]"/>
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:if test="$containers">
<container label="Box" type="Box">
<xsl:for-each select="distinct-values($containers)">
<xsl:sort/>
<xsl:if test="position() != 1">, </xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
</container>
</xsl:if>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Следующее преобразование XSLT 1.0 выполняет то, что вы ищете
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output encoding="utf-8" />
<!-- key to index containers by these three distinct qualities:
1: their ancestor <c??> node (represented as its unique ID)
2: their @type attribute value
3: their node value (i.e. their text) -->
<xsl:key
name = "kContainer"
match = "container"
use = "concat(generate-id(../../..), '|', @type, '|', .)"
/>
<!-- identity template to copy everything as is by default -->
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*" />
</xsl:copy>
</xsl:template>
<!-- special template for <did>s without a <container> child -->
<xsl:template match="did[not(container)]">
<xsl:copy>
<xsl:copy-of select="@*" />
<container label="Box" type="Box">
<!-- from subordinate <container>s of type Box, use the ones
that are *the first* to have that certain combination
of the three distinct qualities mentioned above -->
<xsl:apply-templates mode="list-values" select="
../*/did/container[@type='Box'][
generate-id()
=
generate-id(
key(
'kContainer',
concat(generate-id(../../..), '|', @type, '|', .)
)[1]
)
]
">
<!-- sort them by their node value -->
<xsl:sort select="." data-type="number" />
</xsl:apply-templates>
</container>
<xsl:apply-templates select="node()" />
</xsl:copy>
</xsl:template>
<!-- generic template to make list of values from any node-set -->
<xsl:template match="*" mode="list-values">
<xsl:value-of select="." />
<xsl:if test="position() < last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Возвращает
<c03 id="ref6488" level="file">
<did>
<container label="Box" type="Box">154, 156</container>
<unittitle>Clinic Building</unittitle>
<unitdate era="ce" calendar="gregorian">1947</unitdate>
</did>
<c04 id="ref34582" level="file">
<did>
<container label="Box" type="Box">156</container>
<container label="Folder" type="Folder">3</container>
</did>
</c04>
<c04 id="ref6540" level="file">
<did>
<container label="Box" type="Box">156</container>
<unittitle>Contact prints</unittitle>
</did>
</c04>
<c04 id="ref6606" level="file">
<did>
<container label="Box" type="Box">154</container>
<unittitle>Negatives</unittitle>
</did>
</c04>
</c03>
Часть generate-id () = generate-id (key (...) [1])
называется мюнхианским группированием. Если вы не можете использовать XSLT 2.0, это правильный путь.