Search code examples
xsltxslt-3.0xbrl

Control start and stop position during a for-each


I am looking to find a way to control both start and end numbering that are added as suffix number to text "period". It seems the logic of my code makes for-each count 3 search matches (index 1-3), and due to using if to find se:Bank it reduces printout to 2 prints.

I know I can use position() -2 to force the numbering to start at 0, but that will only work if I have exact same amount of data. As soon as data growths the position() -2 will assume to step 2 positions minus and will not return zero as start.

I do understand that XSLT does what I actually ask for and returns presumable correct answer.

The interval of number I will be using is between 0 and 3. Most of the time in sequence. It will be unknown in advance exact interval amounts.

I know there is a start-at using <xsl:number> but it did not solve my problem. Using start-at prints out same number twice.

The JSON file is aligned with a certain standard so I am not allowed to change the structure of the JSON file.

Suspected problem:

The "foreach" spans over more loops than wanted. In this case I need just for the system to loop twice for "se:Bank" and therefor return "period0" and "period1".

Observation:

I suspect that it probably would be better that I learn to extract when xbrl:concept = se:Bank. That would reduce the planned loop down to 2 search, thus being able iterate over them.

Here you find the xsltfiddle.

Below you find same code as in xsltfiddle:

JSON data:

<data>
{
  "report": {
    "facts": [
      {
        "xbrl:concept": "se:CompanyName",
        "value": "Great Company Ltd"
      },
      {
        "xbrl:concept": "se:Bank",
        "numericValue": 1000
      },
      {
        "xbrl:concept": "se:Bank",
        "numericValue": 3000
      }
    ]
  }
}
</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 periods -->
    
    <xsl:template match="//*[@key='facts']">
        
        <xsl:for-each select="//*[@key='xbrl:concept']">
            <xsl:if test=". = 'se:Bank'">
            
            period<xsl:number value="position()"/>
            </xsl:if>
        </xsl:for-each>
        
    </xsl:template>
  
</xsl:stylesheet>

Result:

<?xml version="1.0" encoding="UTF-8"?>period2period3

Expected result:

<?xml version="1.0" encoding="UTF-8"?>period0period1


Solution

  • If you want to output position() or position() - 1 then <xsl:value-of select="position()"/> or <xsl:value-of select="position() -1 "/>, respectively, suffice, there is no need to feed position() to xsl:number.

    Furthermore, I am not sure I understand your requirements correctly, but using a predicate <xsl:for-each select=".//*[@key='xbrl:concept'][. = 'se:Bank']"> instead of the of the nested xsl:if should help to get the result you want, namely to process the two elements in the input sample meeting the condition in the predicate: https://xsltfiddle.liberty-development.net/93wniUS/1