Reputation: 2493
I need to find the "value/pair" that is equal to xbrl:concept": "se:Bank"
. XSLT finds 2 "value/pairs". Since the data extraction is for a single header I need only 1 heading
answer. In below code I am trying to restrict the xsl:for-each
by using [position() = 1]
but still get the return of the 2 index positions.
This example is minimized to fit a proper question. In the real scenario I am producing about 100-130 headings.
Can this be solved with an for-each
or should I move to another XSLT element?
The code in xsltfiddle
Below you find same code:
JSON
<data>
{
"report": {
"facts": [
{
"xbrl:concept": "se:Bank",
"numericValue": 1000,
"heading": "Bank balance"
},
{
"xbrl:concept": "se:CompanyName",
"value": "Great Company Ltd"
},
{
"xbrl:concept": "se:Bank",
"numericValue": 3000,
"heading": "Bank balance"
}
]
}
}
</data>
XSL:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:output method="xhtml" indent="yes" html-version="5"/>
<xsl:mode on-no-match="shallow-skip"/>
<!-- Parse JSON to XML -->
<xsl:template match="data">
<xsl:apply-templates select="json-to-xml(.)/*"/>
</xsl:template>
<!-- Printout heading -->
<xsl:template match="//*[@key='facts']">
<xsl:for-each select="//*[@key='xbrl:concept'][. = 'se:Bank'][position() = 1]">
<xsl:value-of select="../*[@key='heading']"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Result
Bank balanceBank balance
Wanted result
Bank balance
Upvotes: 0
Views: 428
Reputation: 163458
The construct //*[1]
often causes problems: it parses as /descendant-or-self::node()/child::*[1]
which selects every descendant that is the first child of its parent. What people usually want here is (//*)[1]
which selects the first descendant in the entire document.
The problem is obscured here because it appears in the form of the complex expression //*[@key='xbrl:concept'][. = 'se:Bank'][position() = 1]
but the issue is the same; what you want is (//*[@key='xbrl:concept'][. = 'se:Bank'])[position() = 1]
, or more concisely (//*[@key='xbrl:concept'][. = 'se:Bank'])[1]
.
Upvotes: 3
Reputation: 63019
I'd put the collection of nodes into a variable, then select the first of them
<xsl:template match="//*[@key='facts']">
<xsl:variable name="headings" select="//*[@key='xbrl:concept'][. = 'se:Bank']/../*[@key='heading']"/>
<xsl:value-of select="$headings[1]"/>
</xsl:template>
Or even simpler, pass those nodes to head
<xsl:template match="//*[@key='facts']">
<xsl:value-of select="head(//*[@key='xbrl:concept'][. = 'se:Bank']/../*[@key='heading'])"/>
</xsl:template>
Upvotes: 1
Reputation: 167706
Don't use for-each
, simply select the first element of your sequence with e.g. the head
function:
<xsl:template match="//*[@key='facts']">
<xsl:value-of select="head(fn:map[fn:string[@key = 'xbrl:concept'][. = 'se:Bank']]/fn:string[@key = 'heading'])"/>
Declare xmlns:fn="http://www.w3.org/2005/xpath-functions"
, I don't think it makes sense to ignore the names of elements completely and simply select based on the key
attribute.
Upvotes: 2